Web Worker - Jest - Cannot use 'import.meta' outside a module - reactjs

I'm working on a nextjs 10.1.3 built-in web application. We implemented a web worker to boost up the performance in one of the pages and the plan is to continue adding more workers; also, all the code is properly unit tested, and using the worker-loader in previous webpack versions (4th and below) we were able to test it.
With the new webpack 5 version, the worker-loader plugin is not needed anymore; instead, the way to load a web worker using the new version is new Worker(new URL("#/workers/task.worker.js", import.meta.url));.
Doing it this way, my code is working as expected with npm build && npm start; however, when I try to add the respective unit tests I got the following error: Cannot use 'import.meta' outside a module and everything happens because of the import.meta.url used to add the location of the worker in the browser.
I read many posts on the web regarding babel but I want to get away from that option. Is there any other option to mock the import.meta.url with jest?
Any help will be very welcome. This is the current configuration.
package.json
{
...
"#babel/core": "^7.8.6",
"next": "^10.1.3",
"react": "^16.13.0",
"webpack": "^5.37.1"
"devDependencies": {
...
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.2",
"jest": "^24.9.0",
"jest-cli": "^25.1.0",
...
}
...
}
next.config.js
const {
...
} = process.env;
const basePath = "";
const COMMIT_SHA = [];
const { parsed: localEnv } = require("dotenv").config();
const webpack = require("webpack");
const withBundleAnalyzer = require("#next/bundle-analyzer")({
enabled: process.env.ANALYZE === "true",
});
const nextConfig = {
env: {
NEXT_PUBLIC_COMMIT_SHA: COMMIT_SHA,
},
images: {
domains: [
"...",
],
},
future: {
webpack5: true,
},
productionBrowserSourceMaps: true,
trailingSlash: true,
reactStrictMode: true,
webpack: (config, options) => {
if (localEnv) {
config.plugins.push(new webpack.EnvironmentPlugin(localEnv));
} else {
config.plugins.push(new webpack.EnvironmentPlugin(process.env));
}
config.module.rules.push({
test: /\.(eot|woff|woff2|ttf|svg|png|jpg|gif)$/,
use: {
loader: "url-loader",
options: {
limit: 100000,
name: "[name].[ext]",
},
},
});
config.output = {
...config.output,
chunkFilename: options.isServer
? `${options.dev ? "[name]" : "[name].[fullhash]"}.js`
: `static/chunks/${options.dev ? "[name]" : "[name].[fullhash]"}.js`,
publicPath: `/_next/`,
globalObject: `(typeof self !== 'undefined' ? self : this)`,
};
config.plugins.push(new webpack.IgnorePlugin(/pages.*\/__tests__.*/));
config.plugins.push(
new options.webpack.DefinePlugin({
"process.env.NEXT_IS_SERVER": JSON.stringify(
options.isServer.toString()
),
})
);
return config;
},
};
module.exports = withBundleAnalyzer(nextConfig);
The useEffect worker
useEffect(() => {
if (pageData.data?.length) {
workerRef.current = new Worker(new URL("#/workers/task.worker.js", import.meta.url));
workerRef.current.addEventListener("message", result => {
if (result.error) {
setWorkerError();
} else {
updateData(result.data);
}
});
const ids = pageData.data.map(store => store.id);
workerRef.current.postMessage(ids);
} else {
setNoDataFound();
}
return () => {
workerRef.current && workerRef.current.terminate();
};
}, []);
jest.config.js
module.exports = {
moduleDirectories: ["node_modules", "src", "static", "store"],
modulePathIgnorePatterns: [
"<rootDir>/node_modules/prismjs/plugins/line-numbers",
],
testPathIgnorePatterns: [
"<rootDir>/src/components/component-library",
"<rootDir>/.next",
"jest.config.js",
"next.config.js",
],
collectCoverageFrom: [
"**/src/**",
"**/store/**",
"**/pages/**",
"!**/__tests__/**",
"!**/node_modules/**",
"!**/component-library/**",
],
testEnvironment: "node",
collectCoverage: true,
verbose: false,
automock: false,
setupFiles: ["./setupTests.js"],
moduleNameMapper: {
"#/components/(.*)$": "<rootDir>/src/components/$1",
"#/functions/(.*)$": "<rootDir>/src/components/functions/$1",
"#/services/(.*)$": "<rootDir>/src/components/services/$1",
"#/workers/(.*)$": "<rootDir>/src/components/workers/$1",
"#/scripts(.*)$": "<rootDir>/src/scripts/$1",
"#/src(.*)$": "<rootDir>/src/$1",
"#/__mocks__(.*)$": "<rootDir>/__mocks__/$1",
"#/pages(.*)$": "<rootDir>/pages/$1",
"#/store(.*)$": "<rootDir>/store/$1",
"\\.(css|less)$": "<rootDir>/__mocks__/styleMock.js",
},
coveragePathIgnorePatterns: ["/node_modules/"],
coverageThreshold: {
global: {
branches: 67,
functions: 66,
lines: 73,
statements: 72,
},
},
runner: "groups",
extraGlobals: [],
testTimeout: 10000,
};

In my setup (typescript + ts-jest) I prepended the following node option to make it work:
NODE_OPTIONS=--experimental-vm-modules
Reference can be found here: https://jestjs.io/docs/ecmascript-modules

Related

Next.js vert slow to start on Windows OS

I have this big issue with starting Next.js, the same project on 2 different OS, it has completely different startup times, 1s on MacOSX vs 10s on Windows 11.
Has anyone else had the same problem? Is there any way to fix it?
enter image description here
Here's my next.js conf:
const { resolve } = require('path')
const withBundleAnalyzer = require('#next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
openAnalyzer: true,
})
module.exports = withBundleAnalyzer(
{
i18n: {
locales: ['en', 'it'],
defaultLocale: 'en',
localeDetection: true,
},
compiler: {
removeConsole: process.env.NODE_ENV !== 'development',
},
swcMinify: true,
eslint: {
ignoreDuringBuilds: true,
},
webpack: (config) => {
config.plugins = config.plugins || []
config.optimization.providedExports = true
config.resolve.alias = {
...config.resolve.alias,
'#': resolve(__dirname, './src/'),
}
config.resolve.fallback = { ...config.resolve.fallback, fs: false }
config.module.rules.push({
test: /\.(glsl|vs|fs|vert|frag)$/,
use: ['raw-loader', 'glslify-loader'],
})
return config
},
},
)

Next js jest coverage

I am using in my next js application Cypress and Jest. Running jest --coverage i get an error:
STACK: Error: Duplicate plugin / preset detected.
If you 'd like to use two separate instances of a plugin,
they need separate names, e.g.
plugins: [
['some-plugin', {}],
['some-plugin', {}, 'some unique name'],
]
This is my .babelrc file:
{
"presets": ["next/babel"],
"plugins": ["istanbul"]
}
Who faced with the same issue and how to solve it to get the coverage?
I found the solution that helped to solve the problem.
I had to add the env variable to the .babelrc
{
"env": {
"component": {
"plugins": [
"istanbul"
]
}
}
}
Then add it to cypress.config.js
const { defineConfig } = require('cypress');
const { devServer } = require('#cypress/webpack-dev-server');
const webpackConfig = require('./config/cypress.webpack.config.js');
const codeCoverageTask = require('#cypress/code-coverage/task');
module.exports = defineConfig({
viewportWidth: 1000,
viewportHeight: 660,
video: false,
env: {
BABEL_ENV: 'component',
},
component: {
devServer(devServerConfig) {
return devServer({
...devServerConfig,
framework: 'react',
webpackConfig,
});
},
specPattern: 'src/**/*.cy.{js,ts,jsx,tsx}',
setupNodeEvents(on, config) {
codeCoverageTask(on, config);
return config;
},
},
});

Attempted import error: 'Wallet' is not exported from '#project-serum/anchor'

Trying to import the Wallet class but getting error in title at runtime. This issue apparently should have been fixed in v0.21+ but it doesn't appear to work in my codebase.
Using nextjs v11.0.1 and #project-serum/anchor v0.23.0
Relevant snippet from index.tsx
import { Provider, Program, Wallet } from '#project-serum/anchor';
import { Keypair } from '#solana/web3.js';
const Page = () => {
const testWallet = new Wallet(Keypair.generate());
return <div></div>;
}
The above snippet DOES work when using #project-serum/anchor v0.16.2
next.config.js
const path = require('path');
const withTM = require('next-transpile-modules')([
'#blocto/sdk',
'#project-serum/sol-wallet-adapter',
'#solana/wallet-........,
]);
module.exports = withTM({
target: 'serverless',
distDir: 'build',
trailingSlash: true,
webpack5: false,
webpack(config) {
config.module.rules.push(
{
test: /\.svg$/,
issuer: {
test: /\.(js|ts)x?$/,
},
use: ['#svgr/webpack'],
},
{
test: /\.png$/,
issuer: {
test: /\.(js|ts)x?$/,
},
use: ['file-loader'],
},
{
test: /\.mjs$/,
include: /node_modules/,
type: 'javascript/auto',
},
);
config.resolve.alias = {
...config.resolve.alias,
assets: path.resolve(__dirname, './public/assets'),
};
config.node = {
fs: 'empty',
};
return config;
},
});
tsconfig.json
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"baseUrl": "./src",
"module": "esnext",
"paths": {
"assets/*": ["./public/assets/*"],
"#solana/*": ["./node_modules/#solana/*"]
},
"incremental": true
},
"exclude": ["node_modules"],
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"]
}
Hmm, not exactly sure if this is the same issue I had, but you can try to just re-create the Wallet type locally and imports it.
Looks like this for me
export class MyWallet implements Wallet {
constructor(readonly payer: Keypair) {
this.payer = payer
}
async signTransaction(tx: Transaction): Promise<Transaction> {
tx.partialSign(this.payer);
return tx;
}
async signAllTransactions(txs: Transaction[]): Promise<Transaction[]> {
return txs.map((t) => {
t.partialSign(this.payer);
return t;
});
}
get publicKey(): PublicKey {
return this.payer.publicKey;
}
}
I had to import Wallet like this in react
const { Wallet } = require("#project-serum/anchor");
Use NodeWallet instead of Wallet
import { Provider, Program, NodeWallet } from '#project-serum/anchor';
Using "#project-serum/anchor": "^0.14.0",
import * as anchor from '#project-serum/anchor';
anchor.web3.Keypair.generate().publicKey
anchor.web3.Keypair.generate().secretKey
Looks like, in that version that you are using, Wallet is not available and before, web3 was part of #project-serum/anchor. Then, they moved it into a separete package #solana/web3.js.
Solana dev tools reminds me of the beginning of solidity. Since it is a new and demanding technology, It changes rapidly

Jest react SyntaxError: Cannot use import statement outside a module

I'm trying to run a simple test taken from jest official site in jest but I'm getting this error:
I don't understand why he complains about an import statement since there are not in the test file:
sum.test.ts:
test('two plus two is four', () => {
expect(2 + 2).toBe(4);
});
export {};
Any suggestions?
Following more configurations files about jest and typescript in case are needed for a better view of my situation..
jest.config.js:
// recusrsively replaces the string from / to in the object structure.
function replacePackageJsonSpecific(structure, from, to) {
for (const property in structure) {
if (typeof structure[property] === 'object') {
replacePackageJsonSpecific(structure[property], from, to);
} else if (typeof structure[property] === 'string') {
if (structure[property].includes(from)) {
structure[property] = structure[property].replace(from, to);
}
}
}
}
// replaces properties from left to right, concatenates arrays from left and right
function merge(left, right) {
const merged = {...left, ...right};
for (const property in right) {
if (Array.isArray(right[property]) && Array.isArray(left[property])) {
merged[property] = left[property].concat(right[property]);
}
}
return merged;
}
const path = require('path');
let directory = path.join(process.cwd(), '/');
let reporters = ['default', 'jest-junit'];
process.argv.forEach(function (val) {
if (val.includes('cwd')) {
const cwd = val.split('=')[1];
directory = path.join(directory, cwd.substring(0, cwd.search('test')));
reporters = reporters.slice(0, 1);
}
});
const packageJson = require(directory + 'package.json');
replacePackageJsonSpecific(packageJson.jest, '<rootDir>', directory);
packageJson.jest.rootDir = directory;
const isCI = () => process.env['CI'] === 'true';
module.exports = merge(
{
cacheDirectory: './node_modules/.cache/jest-cache',
reporters: reporters,
roots: [path.join(directory, '/test/unit/')],
setupFilesAfterEnv: [directory + 'test/setupTests.ts'],
snapshotSerializers: ['enzyme-to-json/serializer'],
collectCoverage: process.env['COVERAGE_REPORT'] === 'true' || isCI(),
coverageDirectory: 'generated/coverage',
testEnvironment: 'enzyme',
testEnvironmentOptions: {
enzymeAdapter: 'react16',
},
testMatch: ['**/?(*.)(spec|test).(j|t)s?(x)'],
testURL: 'http://localhost',
preset: 'ts-jest',
transform: {
'^.+\\.(ts|tsx)?$': 'ts-jest',
'^.+\\.(js|jsx|mjs|ts|tsx)$': './node_modules/react-scripts/config/jest/babelTransform.js',
'^.+\\.(js|jsx|mjs)$': './node_modules/babel-jest',
'^.+\\.css$': './node_modules/react-scripts/config/jest/cssTransform.js',
'^(?!.*\\.(js|jsx|mjs|css|json)$)': './node_modules/react-scripts/config/jest/fileTransform.js',
},
transformIgnorePatterns: ['<rootDir>/node_modules/(?!#uwr/icons)'],
coverageReporters: ['html', 'lcov', 'text'],
globals: {
'ts-jest': {
tsConfigFile: 'tsconfig.test.json',
},
},
moduleDirectories: ['node_modules'],
moduleFileExtensions: ['mjs', 'web.ts', 'ts', 'web.tsx', 'tsx', 'web.js', 'js', 'web.jsx', 'jsx', 'json', 'node'],
},
packageJson.jest,
);
babel.config.js:
module.exports = {
presets: [
['#babel/preset-env', {targets: {node: 'current'}}],
'#babel/preset-typescript',
],
};
package.json:
"scripts": {
"test": "react-app-rewired test --no-watchman --watchAll=false",
}
folders structure:

Craco plugin not loading

I'm trying to add this plugin, which is uses this webpack plugin to my craco config file, followed the guide but it's not working.
const CracoAlias = require('craco-alias');
const imageOptimizer = require('craco-image-optimizer-plugin');
module.exports = function ({ env }) {
return {
reactScriptsVersion: 'react-scripts',
style: {
postcss: {
plugins: [
require('tailwindcss'),
require('postcss-focus-visible'),
require('autoprefixer'),
],
},
},
plugins: [
{
plugin: imageOptimizer,
// image-webpack-plugin options
options: {
mozjpeg: {
progressive: true,
quality: 65,
},
// optipng.enabled: false will disable optipng
optipng: {
enabled: false,
},
pngquant: {
quality: [0.65, 0.9],
speed: 4,
},
gifsicle: {
interlaced: false,
},
// the webp option will enable WEBP
webp: {
quality: 75,
},
},
},
{
plugin: CracoAlias,
options: {
//CracoAlias options
},
},
],
};
};
The plugin is supposed to optimize the images, but it's not happening. Any ideas? Is it something wrong with my config file? Thanks.
Seems like it was a problem with react-scripts. Had to add the plugin manually like this:
const { getLoaders, loaderByName } = require('#craco/craco');
module.exports = {
overrideWebpackConfig: ({ webpackConfig, cracoConfig, pluginOptions }) => {
const config = { ...webpackConfig };
const urlLoaderCandidates = getLoaders(config, loaderByName('url-loader'));
const urlLoader = urlLoaderCandidates.matches.find(
m =>
m.loader &&
m.loader.test &&
(Array.isArray(m.loader.test)
? m.loader.test.some(r => r.toString().indexOf('jpe?g') >= 0)
: m.loader.test.toString().indexOf('jpe?g') >= 0)
);
if (!urlLoader) {
throw Error(
'could not find correct url-loader. did you change react-scripts version?'
);
}
const loader = urlLoader.loader;
loader.use = [
{
loader: loader.loader,
options: Object.assign({}, loader.options),
},
{
/**
* #see https://github.com/tcoopman/image-webpack-loader
*/
loader: 'image-webpack-loader',
options: pluginOptions,
},
];
delete loader.loader;
delete loader.options;
return config;
},
};
And then import the file instead of the package.

Resources