How to convert Webpack 4 plugin to Webpack 5 - javascript

How do I convert this plugin that worked on Webpack 4 to Webpack 5?
More specifically, the plugin() function no longer works. How do I replace this to support Webpack 5?
const ConstDependency = require('webpack/lib/dependencies/ConstDependency');
const NullFactory = require('webpack/lib/NullFactory');
class StaticAssetPlugin {
constructor(localization, options, failOnMissing) {
this.options = options || {};
this.localization = localization;
this.functionName = this.options.functionName || '__';
this.failOnMissing = !!this.options.failOnMissing;
this.hideMessage = this.options.hideMessage || false;
}
apply(compiler) {
const { localization } = this;
const name = this.functionName;
compiler.plugin('compilation', (compilation, params) => {
compilation.dependencyFactories.set(ConstDependency, new NullFactory());
compilation.dependencyTemplates.set(ConstDependency, new ConstDependency.Template());
});
compiler.plugin('compilation', (compilation, data) => {
data.normalModuleFactory.plugin('parser', (parser, options) => {
// should use function here instead of arrow function due to save the Tapable's context
parser.plugin(`call ${name}`, function staticAssetPlugin(expr) {
let param;
let defaultValue;
switch (expr.arguments.length) {
case 1:
param = this.evaluateExpression(expr.arguments[0]);
if (!param.isString()) return;
defaultValue = param = param.string;
break;
default:
return;
}
let result = localization(param);
const dep = new ConstDependency(JSON.stringify(result), expr.range);
dep.loc = expr.loc;
this.state.current.addDependency(dep);
return true;
});
});
});
}
}
module.exports = StaticAssetPlugin;
Are there any migration guides for plugin creation that I can follow? Any help would be greatly appreciated.
Thanks.

You can find suitable environment details needed to run the plugin here.
Along with this, you must care about how to access event hooks
compiler.hooks.someHook.tap('MyPlugin', (params) => {
/* ... */
});
You can get more about it here
Converting your existing plugin to Webpack 5, you can tap a specific event hook and get it done.
If you try to run the plugin with the above code with Webpack 5, you will get the below error.
Many articles will suggest you update webpack-cli which is not enough.
const ConstDependency = require('webpack/lib/dependencies/ConstDependency');
const NullFactory = require('webpack/lib/NullFactory');
const PLUGIN_NAME = 'StaticAssetPlugin';
class StaticAssetPlugin {
constructor(localization, options, failOnMissing) {
this.options = options || {};
this.localization = localization;
this.functionName = this.options.functionName || '__';
this.failOnMissing = !!this.options.failOnMissing;
this.hideMessage = this.options.hideMessage || false;
}
apply(compiler) {
const { localization } = this;
const name = this.functionName;
compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation, params) => {
compilation.dependencyFactories.set(ConstDependency, new NullFactory());
compilation.dependencyTemplates.set(ConstDependency, new ConstDependency.Template());
});
compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation, data) => {
data.normalModuleFactory.hooks.parser.for('javascript/auto').tap(PLUGIN_NAME, (parser, options) => {
parser.hooks.expression.for('this').tap(PLUGIN_NAME, function staticAssetPlugin(expr) {
let param;
let defaultValue;
switch (expr.arguments.length) {
case 1:
param = this.evaluateExpression(expr.arguments[0]);
if (!param.isString()) return;
defaultValue = param = param.string;
break;
default:
return;
}
let result = localization(param);
const dep = new ConstDependency(JSON.stringify(result), expr.range);
dep.loc = expr.loc;
this.state.current.addDependency(dep);
return true;
});
})
});
}
}
module.exports = StaticAssetPlugin;
Importantly you have to decide which event hook you need to access from compiler and parser. You will get a list of popular hooks here, for the compiler for parser.
You can get a complete list of hooks just by accessing hooks.
for compiler
console.log(compiler.hooks);
for parser
console.log(parser.hooks);
You can choose accordingly.

Related

command in rust does not get called via invoke, no error message

I've got 3 commands i am calling from the front end, 2 of them work perfectly, the third does not.
The issue lies with the function tournament_search
main.rs:
fn main() {
tauri::Builder::default()
.manage(ApiKey {key: Default::default()})
.invoke_handler(tauri::generate_handler![set_api_key, check_connection, tournament_search])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
#[tauri::command]
fn set_api_key(key: String , state: State<ApiKey>){
let mut api_key = state.key.lock().unwrap();
*api_key = key;
}
#[tauri::command]
async fn check_connection(api_key: State<'_, ApiKey>) -> Result<bool, ()> {
let key = api_key.key.lock().unwrap().clone();
let res = Client::new().get(API_URL).bearer_auth(key).send().await.unwrap().text().await.unwrap();
let json: Value = serde_json::from_str(res.as_str()).unwrap();
match json["success"].as_bool() {
Some(_x) => Ok(false),
None => Ok(true)
}
}
#[tauri::command]
async fn tournament_search(search_string: String, api_key: State<'_, ApiKey>) -> Result<&str, ()> {
println!("test: {}", search_string);
let key = api_key.key.lock().unwrap().clone();
let mut query: String = String::new();
query.push_str("query($name:String){tournaments(query:{filter:{name:$name}}){nodes{name,slug,id}}},{$name:");
query.push_str(search_string.as_str());
query.push_str("}");
let res = Client::new().get(API_URL).bearer_auth(key).body(query).send().await.unwrap().text().await.unwrap();
println!("{}", res);
Ok("")
}
index.js:
const { invoke } = window.__TAURI__.tauri
window.addEventListener("load", (ev) => {
let test = document.getElementById("test");
let apiKey = document.getElementById("apiKey");
let tournamentSearch = document.getElementById("tournamentSearch");
let tourneyList = document.getElementById("tourneyList");
apiKey.addEventListener("input", (ev) => {
invoke("set_api_key", {key: apiKey.value});
invoke("check_connection").then((res) => {
if(res){
tournamentSearch.disabled = false;
}else{
tournamentSearch.disabled = true;
}
});
});
tournamentSearch.addEventListener("input", (ev) => {
test.innerText = "e";
invoke('tournament_search', {search_string: tournamentSearch.value}).then((res) => {
test.innerText = res;
});
});
});
Already looked for zero width characters, whether the event get's called in js etc. The issue is just that the function is not called.
You'd only see an error message by adding a .catch() to the invoke call.
Anyawy, the issue here is that Tauri converts command arguments to camelCase on the rust side (to match the JS default) so it would be { searchString: tournamentSearch.value } instead.
If you'd prefer snake_case instead, you can tell Tauri to use that for arguments by changing the command like this:
#[tauri::command(rename_all = "snake_case")]

How to find the calling test in cypress custom command

I have a command that overwrites pause to add the input from a dialog to the reports. I want to know if there is a way to know what test is calling the function so that I can customize the message on the inputs.
Cypress.Commands.overwrite('pause', (originalFn, element, options) => {
var tryThis = '';
if (//place calling the function == file1) {
tryThis = 'message1';
} else if (//place calling the function == file2) {
...
} else if (//place calling the function == file3) {
...
}
var datalog = window.prompt(tryThis, "Log your results");
cy.addContext("DATALOG:" + datalog);
return originalFn(element, options)
})
As well as access via the Mocha properties there is also
For the spec file Cypress.spec
Properties for my.spec.js
Cypress.spec.absolute: "C:/.../my.spec.js"
Cypress.spec.name: "my.spec.js"
Cypress.spec.relative: "cypress\integration\my.spec.js"
Cypress.spec.specFilter: "my"
Cypress.spec.specType: "integration"
For the test cy.state('runnable')
For
describe('my-context', () => {
it('my-test', () => {
Properties and methods,
const title = cy.state('runnable').title; // "my-test"
const fullTitle = cy.state('runnable').fullTitle(); // "my-context my-test"
const titlePath = cy.state('runnable').titlePath(); // ["my-context", "my-test"]
You can also add metadata to the test
describe('my-context', () => {
it('my-test', { message: "my-message" }, () => {
and grab it in the command overwrite
const message = cy.state('runnable').cfg.message; // "my-message"
I tried this and it worked for me (my version of cypress is 6.1.0):
cy.log(Cypress.mocha.getRunner().suite.ctx.test.title);
More info: https://github.com/cypress-io/cypress/issues/2972

How to implement Command Pattern with async/await in JS

I'm currently implementing a WebSocket connection and I'm using a command pattern approach to emit some messages according to the command that users execute.
This is an abstraction of my implementation:
let socketInstance;
const globalName = 'ws'
const globalObject = window[globalName];
const commandsQueue = isArray(globalObject.q) ? globalObject.q : [];
globalObject.q = {
push: executeCommand
};
commandsQueue.forEach(command => {
executeCommand(command);
});
function executeCommand(params) {
const actions = {
create,
send
};
const [command, ...arg] = params;
if (actions[command]) {
actions[command](arg);
}
}
function send([message]) {
socketInstance.send(message);
}
function create([url]) {
socketInstance = new WebSocket(url);
}
In order to start sending messages, the user should be run:
window.ws.push('create', 'ws://url:port');
window.ws.push('send', 'This is a message');
The problem that I have is the connection is async, and I need to wait until the connection is done to continue to the next command. Is it a good idea to implement an async/await in commandsQueue.forEach or an iterator is a better approach? What other best approaches do you recommend?
The solution that I'm using right now is: I created an empty array of messages at the beginning and then every time that I call the send command I verify if the connection wasn't opened and I added to this array.
Something like that:
const messages = [];
let socketInstance;
let isConnectionOpen = false;
const globalName = "ws";
const globalObject = window[globalName];
const commandsQueue = isArray(globalObject.q) ? globalObject.q : [];
globalObject.q = {
push: executeCommand,
};
commandsQueue.forEach((command) => {
executeCommand(command);
});
function executeCommand(params) {
const actions = {
create,
send,
};
const [command, ...arg] = params;
if (actions[command]) {
actions[command](arg);
}
}
function send([message]) {
if (isConnectionOpen) {
socketInstance.send(message);
} else {
messages.push(message);
}
}
function onOpen() {
isConnectionOpen = true;
messages.forEach((m) => {
send([m]);
});
messages.length = 0;
}
function create([url]) {
socketInstance = new WebSocket(url);
socketInstance.onopen = onOpen;
}

how to unsubscribe of function in method?

Working on dialog component with angular js and now I find out that my function is subscribed and in if condition do not quit method, but continuously executing another function afterClosed() , here is example of code :
openCreateNewContentDialog(): void {
const oldData = this.dataSource.data;
const dialogConfig = AppConstants.matDialogConfig();
const dialog = this.dialog.open(LicenseDialogComponent, dialogConfig);
dialog.beforeClosed().subscribe(licenceDate => {
for (const datesToCheck of oldData) {
const newDateFrom = new Date(licenceDate.expirationDateFrom);
const oldDateTo = new Date(datesToCheck.expirationDateTo.toString());
if (newDateFrom <= oldDateTo) {
// console.log('return?');
return;
}
}
});
dialog.afterClosed().subscribe(licence => {
if (licence) {
this._value.push(licence);
this.dataSource.data = this.value;
this.change();
}
});
}
What is the best and optimized way to unsubscribe beforeClosed() function?
So from your description, I understand that you dont want a second subscription to happen if the condition in the first subscriber is true, right? But you subscription will happen anyway because you instantiated it in the method, the code in the subscribe() it's just a callback. So if you dont want a lot of rewriting I will suggest storing
subscriptions in variables, so you will have an access to them and can unsubscribe at any time.
openCreateNewContentDialog(): void {
const oldData = this.dataSource.data;
const dialogConfig = AppConstants.matDialogConfig();
const dialog = this.dialog.open(LicenseDialogComponent, dialogConfig);
const beforeClosed = dialog.beforeClosed().subscribe(licenceDate => {
for (const datesToCheck of oldData) {
const newDateFrom = new Date(licenceDate.expirationDateFrom);
const oldDateTo = new Date(datesToCheck.expirationDateTo.toString());
if (newDateFrom <= oldDateTo) {
// console.log('return?');
afterClosed.unsubscribe();
return;
}
}
});
const afterClosed = dialog.afterClosed().subscribe(licence => {
if (licence) {
this._value.push(licence);
this.dataSource.data = this.value;
this.change();
}
});
}
I hope it helps! Also you can try https://www.digitalocean.com/community/tutorials/angular-takeuntil-rxjs-unsubscribe if you have to handle multiple subscriptions.

Covering tests using wildcard path for test files in mocha integration tests

top.js
const opts = require('minimist')(process.argv);
const failAfter = Number(opts['fail-after'] );
function importTest(name, path) {
describe(name, function() {
require(path);
});
}
describe('top', function() {
var fileMatches = null;
let failures = 0;
afterEach(function() {
console.log(this.currentTest.state);
if(this.currentTest.state == 'failed') {
console.log("fail-after = " +failAfter);
failures++;
console.log("failures = " +failures);
if(failures == failAfter){
console.log('\nToo many failures, exiting...');
process.exit(1);
}
}
});
importTest("a",'./test/a/a.js');
importTest("b",'./test/b/b.js');
importTest("test",'./test/test.js');
importTest("test1",'./test/test1.js');
});
I am trying to wrap all my mocha integration tests written in different files in my 'top' describe(written above). The purpose of this is to stop the tests after a particular number of test case failures(specified by the user in 'fail-after').
To include all the tests I have to explicitly call importTest() and pass the path of each test file. I want to automate it, so that I do not have to give the path of each file and instead all the files in the directory and sub-directories are covered.
Can someone suggest a way to do the same?
I run my tests using command "mocha --fail-after=5 ./top.js"
Solved the above problem by using the Glob npm module.
Solution:
top.js
const opts = require('minimist')(process.argv);
const failAfter = Number(opts['fail-after'] );
var Glob = require("glob");
var pattern = "*.js";
var options = {matchBase: true, nonull: true};
function importTest(name, path) {
describe(name, function() {
require(path);
});
}
describe('top', function () {
let failures = 0;
afterEach(function () {
console.log(this.currentTest.state);
if (this.currentTest.state == 'failed') {
failures++;
console.log("failures = " + failures);
if (failures == failAfter) {
console.log('\nToo many failures, exiting...');
process.exit(1);
}
}
});
function globArray(pattern, options) {
var list = [];
var pathList = Glob.sync(pattern, options);
pathList.forEach(function (item) {
list.push("./"+item);
});
return list;
}
var mg = globArray(pattern, options);
for(var i=0; i<mg.length; i++) {
importTest("sample", mg[i]);
}
});

Categories