Loading files from all directories inside specific directory - node.js

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;

Related

how to upgrade webpack from 3 to 5

I really need someone's help.
In this repo,
https://github.com/ueno-llc/starter-kit-universally.git
I don't know how to upgrade the webpack.
The error is from new webpack.NamedChunksPlugin, new webpack.optimize.CommonsChunkPlugin
I can't fix it by myself...
Can anyone help me for this issue ?
import fs from 'fs';
import _isArray from 'lodash/isArray';
import _get from 'lodash/get';
import appRootDir from 'app-root-dir';
import path from 'path';
import webpack from 'webpack';
import ExtractCssChunks from 'extract-css-chunks-webpack-plugin';
import ExtractTextPlugin from 'extract-text-webpack-plugin';
import ChunkManifestWebpackPlugin from 'chunk-manifest-webpack-plugin';
import NameAllModulesPlugin from 'name-all-modules-plugin';
import config from '../';
import { removeNil } from '../../internal/utils/arrays';
import { ifElse } from '../../internal/utils/logic';
function externals() {
// UENO: Define externals
// We don't want our node_modules to be bundled with any bundle that is
// targetting the node environment, prefering them to be resolved via
// native node module system.
// Some of our node_modules may contain files that depend on our
// webpack loaders, e.g. CSS or SASS.
// For these cases please make sure that the file extensions are
// registered within the following configuration setting.
const whitelist = [
/\.bin/,
'source-map-support/register',
'react-universal-component',
'webpack-flush-chunks',
]
// And any items that have been whitelisted in the config need
// to be included in the bundling process too.
.concat(config('nodeExternalsFileTypeWhitelist') || []);
return fs
.readdirSync(path.resolve(appRootDir.get(), 'node_modules'))
.filter(x => !whitelist.some((w) => {
if (w instanceof RegExp) {
return w.test(x);
}
return x === w;
}))
.reduce((ext, mod) => {
// mark this module as external
// https://webpack.js.org/configuration/externals
ext[mod] = `commonjs ${mod}`;
return ext;
}, {});
}
export default (webpackConfig, buildOptions) => {
const { target, optimize = false, localIdentName } = buildOptions;
const isProd = optimize;
const isDev = !isProd;
const isClient = target === 'client';
const isNode = !isClient;
const ifNode = ifElse(isNode);
const ifClient = ifElse(isClient);
const ifProdClient = ifElse(isProd && isClient);
// Overwrite the externals because apparently `webpack-node-externals` does not
// work well with `webpack-flush-chunks`
if (isNode) {
webpackConfig.externals = [externals()];
}
// Remove ExtractTextPlugin
const etpIndex = webpackConfig.plugins.findIndex(p => p instanceof ExtractTextPlugin);
if (etpIndex > -1) {
webpackConfig.plugins.splice(etpIndex, 1);
}
// Add some plugins for css code splitting
webpackConfig.plugins.push(
...removeNil([
// NamedModulesPlugin, NamedChunksPlugin and NameAllModulesPlugin (see below) are all here to
// deal with chunk hashes.
// See https://medium.com/webpack/predictable-long-term-caching-with-webpack-d3eee1d3fa31
ifClient(new webpack.NamedModulesPlugin()),
ifClient(
new webpack.NamedChunksPlugin((chunk) => {
if (chunk.name) {
return chunk.name;
}
return chunk.mapModules(m => path.relative(m.context, m.request)).join('_');
}),
),
ifClient(new ExtractCssChunks({
filename: isDev ? '[name].js' : '[name]-[contenthash].css',
})),
ifProdClient(new ChunkManifestWebpackPlugin({
filename: '../manifest.json',
manifestVariable: '__WEBPACK_MANIFEST__',
})),
// To make sure chunk hashes stay the same if their contents don’t change
// see: https://webpack.js.org/guides/caching/#module-identifiers
ifClient(new webpack.HashedModuleIdsPlugin()),
// Add vendor code chunk
ifProdClient(
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
filename: '[name]-[chunkhash].js',
// Put all node_modules into one chunk
// see: https://webpack.js.org/plugins/commons-chunk-plugin/#passing-the-minchunks-property-a-function
minChunks: module => module.context && module.context.includes('node_modules'),
}),
),
// Add webpack boilerplate chunk
ifClient(
new webpack.optimize.CommonsChunkPlugin({
name: 'bootstrap', // needed to put webpack bootstrap code before chunks
filename: isDev ? '[name].js' : '[name]-[chunkhash].js',
}),
),
ifClient(new NameAllModulesPlugin()),
// We only want one server chunk
ifNode(
new webpack.optimize.LimitChunkCountPlugin({
maxChunks: 1,
}),
),
]),
);
const { rules } = webpackConfig.module;
const moduleRules = [..._get(rules, '0.oneOf', rules)];
// Overwrite css loader ExtractTextPlugin
const cssRule = moduleRules.find(r => r.test.test('.css'));
if (cssRule && _isArray(cssRule.use)) {
// Find plugin
const pluginIndex = cssRule.use.findIndex(u =>
Object.prototype.hasOwnProperty.call(u, 'loader') && /extract-text-webpack-plugin/.test(u.loader));
if (pluginIndex > -1) {
const loaders = ExtractCssChunks.extract({
fallback: 'style-loader',
use: [
`css-loader?modules=1&importLoaders=1&localIdentName=${localIdentName}`,
'postcss-loader',
'sass-loader?outputStyle=expanded',
],
});
cssRule.use.splice(
pluginIndex,
loaders.length,
...loaders,
);
}
}
// Overwrite node_modules css loader ExtractTextPlugin
const nmCssRule = moduleRules.find(r =>
r.test.test('node_modules.css') &&
(!r.exclude || !r.exclude.test('node_modules.css')));
if (nmCssRule && _isArray(nmCssRule.use)) {
// Find plugin
const pluginIndex = nmCssRule.use.findIndex(u =>
Object.prototype.hasOwnProperty.call(u, 'loader') && /extract-text-webpack-plugin/.test(u.loader));
if (pluginIndex > -1) {
const loaders = ExtractCssChunks.extract({
fallback: 'style-loader',
use: ['css-loader', 'postcss-loader'],
});
nmCssRule.use.splice(
pluginIndex,
loaders.length,
...loaders,
);
}
}
return webpackConfig;
};
===
I have tried to use webpack.config.js, but still failed...
Please help me :(

How to mock #google-cloud/kms using jest

I'm trying to write unit test cases for decrypt. I've my own implementation of decrypting an encrypted file. While trying to import the decrypt.mjs facing the following error.
Must use import to load ES Module: /node_modules/bignumber.js/bignumber.mjs
My application is a react frontend and NodeJS backend. I've used ES6 modules for NodeJS. Here is my decrypt.mjs file
import { readFile } from 'fs/promises';
import path from 'path';
import { KeyManagementServiceClient } from '#google-cloud/kms';
const decrypt = async (APP_MODE, __dirname) => {
if (APP_MODE === 'LOCALHOST') {
const keys = await readFile(
new URL(`./stagingfile.json`, import.meta.url)
).then((data) => JSON.parse(data));
return keys;
}
const { projectId, locationId, keyRingId, cryptoKeyId, fileName } =
getKMSDefaults(APP_MODE);
const ciphertext = await readFile(
path.join(__dirname, `/${fileName}`)
);
const formattedName = client.cryptoKeyPath(
projectId,
locationId,
keyRingId,
cryptoKeyId
);
const request = {
name: formattedName,
ciphertext,
};
const client = new KeyManagementServiceClient();
const [result] = await client.decrypt(request);
return JSON.parse(result.plaintext.toString('utf8'));
};
const getKMSDefaults = (APP_MODE) => {
//Based on APP_MODE the following object contains different values
return {
projectId: PROJECT_ID,
locationId: LOCATION_ID,
keyRingId: KEY_RING_ID,
cryptoKeyId: CRYPTO_KEY_ID,
fileName: FILE_NAME,
};
};
export default decrypt;
I tried to mock the #google-cloud/kms using manual mock (jest) but it didn't work. I tried multiple solutions to mock but nothing worked and it ended with the Must use import to load ES Module error.
I've had successfully used jest to mock #google-cloud/kms with TypeScript, so hopefully this will be the same process for ES modules that you can use.
Example working code:
// jest will "hoist" jest.mock to top of the file on its own anyway
jest.mock("#google-cloud/kms", () => {
return {
KeyManagementServiceClient: jest.fn().mockImplementation(() => {
return {
encrypt: kmsEncryptMock,
decrypt: kmsDecryptMock,
cryptoKeyPath: () => kmsKeyPath,
};
}),
};
});
// give names to mocked functions for easier access in tests
const kmsEncryptMock = jest.fn();
const kmsDecryptMock = jest.fn();
const kmsKeyPath = `project/location/keyring/keyname`;
// import of SUT must be after the variables used in jest.mock() are defined, not before.
import { encrypt } from "../../src/crypto/google-kms";
describe("Google KMS encryption service wrapper", () => {
const plaintext = "some text to encrypt";
const plaintextCrc32 = 1897295827;
it("sends the correct request to kms service and raise error on empty response", async () => {
// encrypt function is async that throws a "new Error(...)"
await expect(encrypt(plaintext)).rejects.toMatchObject({
message: "Encrypt: no response from KMS",
});
expect(kmsEncryptMock).toHaveBeenNthCalledWith(1, {
name: kmsKeyPath,
plaintext: Buffer.from(plaintext),
plaintextCrc32c: { value: plaintextCrc32 },
});
});
});

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

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.

Tutorial help: Import name doesn't align with component name, still works

I am following the project on github and working through personal modifications
It could probably be answered with google/documentation, but I don't know the right keywords to pull it in yet. One day, soon.
This doesn't line up with the file naming conventions I have seen.
Heroes.js imports api.js. api.js talks to the express backend via a proxy setting at the /api/* route. Problem is that the api.js file never declares an "api" component.
How is it that api.js is exporting "heroService", yet when its being imported, its operating under "api"? Is it because the file is named api.js and it defaults to that as the component name?
Heroes.js
import api from '../api';
//Example function
handleDelete(event, hero) {
event.stopPropagation();
api.destroy(hero).then(() => {
let heroes = this.state.heroes;
heroes = heroes.filter(h => h !== hero);
this.setState({ heroes: heroes });
if (this.selectedHero === hero) {
this.setState({ selectedHero: null });
}
});
}
api.js
const baseAPI = '/api';
const heroService = {
//example function
destroy(hero) {
return new Promise((resolve, reject) => {
fetch(`${baseAPI}/hero/${hero.id}`, { method: 'DELETE' })
.then(response => response.json())
.then(json => resolve(json))
.catch(err => {
reject(err);
});
});
}
export default heroService;
api.js is exporting a default.
Since this is not a named export, the binding takes on the name that you specified upon import.
As you wrote import api from '../api';, you use api.destroy().
If you wrote import monkeys from '../api';, you would use monkeys.destroy() instead.
References:
https://developer.mozilla.org/en-US/docs/web/javascript/reference/statements/export
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import

Resources