Custom Babel plugin – Change a stringLiteral value to actual Javascript code - javascript

I'm writing a plugin which will be kind of my own lightweight babel-plugin-react-css-modules implementation, anyways…
the plugin needs to change the value of the className attribute in JSX elements from a string to an array. Essentially it should map the classNames string to an array "class-one class-two" => [object["class-one"] || "class-one", object["class-two"] || "class-two"];
The part I struggle with is just making babel output JS instead of a string, which I'm not sure how to do.
module.exports = () => {
const styles = "styles" + Math.random().toString(36).substr(2, 9);
let foundStyleImport;
return {
visitor: {
ImportDeclaration: (path) => {
if (
!/(sass|scss)\.js$/.test(path.node.source.value) &&
path.node.specifiers.length > 0
) return;
foundStyleImport = true;
path.node.specifiers[0] = {
type: "ImportDefaultSpecifier",
local: {
type: "Identifier",
name: styles,
loc: { identifierName: styles }
}
}
},
// The part I'm struggling with ⬇
JSXAttribute: ({node: {value, name}}) => {
if (!foundStyleImport || name.name !== "className") return;
// changing this from "stringLiteral" to "arrayExpression"
// results in an error…
value.type = "arrayExpression";
value.value = `[${value.value.split(" ").map(cls => {
return `${styles}["${cls}"] || "${cls}"`;
}).join(", ")}]`;
}
}
};
}
Babel should take this:
import "./header.sass.js";
import { h, Fragment } from "/web_modules/preact.js";
export default function () {
return h("header", {
className: "foobar"
}, h("p", null, "Hello"));
}
And output something like this this:
import styles0u33w7qps from "./header.sass.js";
import { h, Fragment } from "/web_modules/preact.js";
export default function () {
return h("header", {
className: [styles0u33w7qps["foobar"] || "foobar"]
}, h("p", null, "Hello"));
}

Related

Compare nested objects and list out differences in JavaScript

I want the difference in such a way that the I don't return the entire nested object if any of the values is different.
I have seen solutions online and they all return the entire nested objects and it doesn't work if only 1 key-value pair is changed. i don't want to show the difference as a complete nested object. it should be easier for any user to read.
for eg:
const A = {
position: 2,
attributes: [{
code: 123,
name: "xyz",
params: {
label: "hehe",
units: "currency"
}
}],
code: 1
}
const B = {
position: 3,
attributes: [{
code: 123,
name: "xyzr",
params: {
label: "heh",
units: "currency"
}
}],
code: 1
}
I want the output to be like this:
difference: {
position: {
current: 2,
previous: 3
},
attributes: {
current : [{ name: "xyz", params: { label: "hehe" } }],
previous: [{ name: "xyzr", params: {label: "heh"}}]
}
}
The code that I tried:
const compareEditedChanges = (A: any, B: any) => {
const allKeys = _.union(_.keys(A), _.keys(B));
try {
setDifference(
_.reduce(
allKeys,
(result: any, key) => {
if (!_.isEqual(A?.[key], B?.[key])) {
result[key] = {
current: A[key],
previous: B[key]
};
}
return result;
},
{}
)
);
} catch (err) {
console.error(err);
}
return difference;
};
After giving it a lot of thought to the code, I came with my own solution for a deeply nested objects comparison and listing out the differences in an object with keys as current and previous.
I didn't use any inbuilt libraries and wrote the code with simple for loop, recursion and map
const compareEditedChanges = (
previousState,
currentState
) => {
const result = [];
for (const key in currentState) {
// if value is string or number or boolean
if (
typeof currentState[key] === 'string' ||
typeof currentState[key] === 'number' ||
typeof currentState[key] === 'boolean'
) {
if (String(currentState[key]) !== String(previousState[key])) {
result.push({
[key]: {
current: currentState[key],
previous: previousState[key]
}
});
}
}
// if an array
if (
Array.isArray(currentState[key]) ||
Array.isArray(previousState[key])
) {
console.log(currentState[key])
if (currentState[key].length > 0 || previousState[key].length > 0) {
currentState[key].map((value, index) => {
// check for array of string or number or boolean
if (
typeof value === 'string' ||
typeof value === 'number' ||
typeof value === 'boolean'
) {
if (
JSON.stringify(currentState[key]) !==
JSON.stringify(previousState[key])
) {
result.push({
[key]: {
current: currentState[key],
previous: previousState[key]
}
});
}
}
// check for array of objects
if (typeof value === 'object') {
const ans = compare(
value,
previousState[key][index]
);
result.push(ans);
}
});
}
}
}
return result;
};
You first need a object:
const [object, setObject] = useState({
number: 0,
text: "foo"
});
You need to check when the object changed with useEffect, but you also need to see the previos object, for that we will be using a helper function.
const prevObject = usePrevious(object);
const [result, setResult] = useState("");
useEffect(() => {
if (prevObject) {
if (object.number != prevObject.number) {
setResult("number changed");
}
if (object.text != prevObject.text) {
setResult("text changed");
}
}
}, [object]);
//Helper function to get previos
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
}
Here is the Codesandbox

ckEditor Error : editor-isreadonly-has-no-setter

I'm using ckeditor5 balloon block mode in nuxt project.
I have used online builder and downloaded build files , add the build files to my project and importing them into my editor component and using it!
the only problem that I have is that when the page loads ,
I get an error : editor-isreadonly-has-no-setter.
I tried binding v-model to the editor but the value won't be updated!
note : I have used ckeditor5 classic mode identical to the way that I'm using Balloon Block, donno really what's going on!
this is my component :
<template>
<ckeditor
:id="id"
v-bind="$attrs"
:editor="BalloonBlock"
:config="editorConfig"
v-on="$listeners"
/>
</template>
<script>
let BalloonBlock
let CKEditor
if (process.client) {
BalloonBlock = require('#/plugins/ckeditor/ckeditor')
CKEditor = require('#ckeditor/ckeditor5-vue2')
} else {
CKEditor = { component: { template: '<div></div>' } }
}
export default {
name: 'CKEditor',
components: {
ckeditor: CKEditor.component,
},
props: {
fillErr: {
type: Boolean,
default: false,
required: false,
},
minHeight: {
type: String,
default: '350px',
required: false,
},
label: {
type: String,
default: '',
required: false,
},
},
data() {
return {
classicEditor: BalloonBlock,
editorConfig: {
language: 'fa',
contentsLangDirection: 'rtl',
},
editorElement: null,
id: null,
}
},
computed: {
value() {
return this.$attrs.value
},
},
created() {
this.id = this.uuidv4()
},
mounted() {
if (!document.getElementById('editorFaTranslate')) {
const faScript = document.createElement('script')
faScript.setAttribute('charset', 'utf-8')
faScript.setAttribute('type', 'text/js')
faScript.setAttribute('id', 'editorFaTranslate')
faScript.setAttribute(
'src',
require('##/plugins/ckeditor/translations/fa.js')
)
document.head.appendChild(faScript)
}
const intervalId = setInterval(() => {
const ckEditor = document.getElementById(this.id)
if (ckEditor) {
clearInterval(intervalId)
this.editorElement = ckEditor
}
})
},
methods: {
uuidv4() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(
/[xy]/g,
function (c) {
const r = (Math.random() * 16) | 0
const v = c === 'x' ? r : (r & 0x3) | 0x8
return v.toString(16)
}
)
},
insertTextAtTheEnd(text) {
function findCorrectPosition(htmlStr) {
const lastIndexOfHTMLTag = htmlStr.lastIndexOf('</')
const lastUlTag = htmlStr.lastIndexOf('</ul>')
const lastOlTag = htmlStr.lastIndexOf('</ol>')
if (
lastUlTag === lastIndexOfHTMLTag ||
lastOlTag === lastIndexOfHTMLTag
) {
const lastLiTag = htmlStr.lastIndexOf('</li>')
return lastLiTag
}
return lastIndexOfHTMLTag
}
const currentString = this.value
const correctIndex = findCorrectPosition(currentString)
const firstHalf = currentString.substring(0, correctIndex)
const secondHalf = currentString.substring(correctIndex)
const newString = `${firstHalf}${text}${secondHalf}`
this.$emit('input', newString)
},
},
}
</script>
I would welcome any idea!
I added "#ckeditor/ckeditor5-vue2": "github:ckeditor/ckeditor5-vue2", in my dependencies and all of a sudden my problem was gone!

Vue 3 changing the length of both arrays have same values intially

I am using vue 3 where is i am receiving an array of associate schedule from server. Now i am saving this schedule to 2 arrays. I am doing this because i need the original fetched data later after doings changes in associate list array which is my first array.
associateList
orignalList
The problem is when I am replacing the associate array after doing changes with original array .No nothing works infact original list contains same changes which i did on associate list array even i have not touched the original list anywhere in my code just saving the data from response on it. I just want the original res on original list array so i can replace associate list with original array when watch function detect changes in attendance list array.
<script lang="ts">
import { Options, Vue } from "vue-class-component";
import ApprovalService from "../../service/ApprovalService";
import Toaster from "../../helpers/Toaster";
import moment from "moment";
import { camelCase } from "lodash";
import {
ScheduleList,
AttendanceList,
ApprovedList,
} from "../hoursApproval/IHoursAppoval";
import VueCal from "vue-cal";
import "vue-cal/dist/vuecal.css";
import AssociatePinVerification from "../../components/AssociatePinVerification.vue";
#Options({
components: { VueCal, AssociatePinVerification },
watch: {
attendanceList() {
const oL = this.orignalList;
alert('orgi'+oL.length);
this.associateList = this.orignalList;
this.checkScheduleContainsLogedHrs();
},
},
})
export default class HoursApproval extends Vue {
private ApprovalTxn;
private scheduleID = "";
private toast;
private orignalList: ScheduleList[] = [];
private associateList: ScheduleList[] = [];
private approvedList: ScheduleList[] = [];
private attendanceList: AttendanceList[] = [];
private approveManually = {
hours: 0,
freezed: false,
shiftDate: "",
counterId: 0,
};
//DEFAULT METHOD OF TYPE SCRIPT
//CALLING WHENEVER COMPONENT LOADS
created() {
this.ApprovalTxn = new ApprovalService();
this.toast = new Toaster();
}
mounted() {
this.getSchedule();
}
getSchedule() {
this.ApprovalTxn.getAssociateShifts(this.searchDate).then((res) => {
const d = this.camelizeKeys(res);
const s = d.employeeList.scheduleList;
if (s != null)
{
this.orignalList = this.camelizeKeys(d.employeeList.scheduleList);
this.associateList = this.camelizeKeys(d.employeeList.scheduleList);
}
else
{
this.associateList = [];
this.orignalList = [];
}
this.scheduleID = d.employeeList.id;
this.weekStartingDate = d.postStartingDate;
this.weekEndingDate = d.postEndingDate;
this.weekNo = d.weekNo;
});
}
camelizeKeys = (obj) => {
if (Array.isArray(obj)) {
return obj.map((v) => this.camelizeKeys(v));
} else if (obj !== null && obj.constructor === Object) {
return Object.keys(obj).reduce(
(result, key) => ({
...result,
[camelCase(key)]: this.camelizeKeys(obj[key]),
}),
{}
);
}
return obj;
};
formatDate(value) {
if (value) {
return moment(String(value)).format("DD-MM-YYYY");
}
}
updateAssociateLogin() {
if (
this.loginDetails.loginTime == "" ||
this.loginDetails.logoutTime == "" ||
this.loginDetails.loginDate == ""
) {
this.toast.showWarning(
"Please set date login and logout timings for associate to proceed"
);
} else {
this.associateList = [];
this.ApprovalTxn.updateAssociateLogin(
this.loginDetails.loginTime,
this.loginDetails.attendenceID,
this.managerApproved,
this.loginDetails.logoutTime,
this.loginDetails.loginDate,
this.weekStartingDate,
this.weekEndingDate
).then((res) => {
this.toast.handleResponse(res);
alert(this.orignalList.length);
// this.associateList = this.orignalList;
const d = this.camelizeKeys(res);
//DOING THIS TO CHNAGE THE RE ACTIVITY OF VUE
//this.modifyTimings();
this.attendanceList = d.data;
//alert(this.orignalList.length);
//console.log(this.associateList);
});
this.loginHoursDialog = false;
}
}
}
</script>

ES2015 + flow : self-referenced (circular ?) enums

Using rollup, buble, flow-remove-types,
Is it possible to create an ENUM of classes instances for chess board representation, as types, like this:
// a Ref is a class or a type
class Ref { /* ... */ }
// Refs is an ENUM
Refs.forEach((ref: Ref, key: string) => {
console.log(key) // outputs: "a1", ..., "h8" successively
})
// type checking should work
typeof Refs.a1 === Ref // true
// etc...
typeof Refs.h8 === Ref // true
// move(ref) --> ref will work
Refs.a1.move(7, 7) === Refs.h8 // true
Refs.h8.move(-7, -7) === Refs.h8 // true
// with...
Refs.a1.move(0, 0) === Refs.a1 // true
// void reference
Refs.a1.move(-1, -1) === null
// or
Refs.a1.move(-1, -1) === Refs.EMPTY
A possible modular implementation would be packing the Ref class and the Refs collection in the same file, with a initialization code, like Enuify lib does... But how to make the Ref#move method working properly ??
The same as :
TicTacToe.X.us =TicTacToe.X
TicTacToe.X.them =TicTacToe.O
TicTacToe.O.us =TicTacToe.O
TicTacToe.O.them =TicTacToe.X
something like this, is perfectible, but works fine for me...
type TF = 'a'|'b'|'c'|'d'|'e'|'f'|'g'|'h'
type TR = '1'|'2'|'3'|'4'|'5'|'6'|'7'|'7'
type TRefDef = {
file: TF,
fidx: number,
rank: TR,
ridx: number
}
interface IRef {
move (df: number, dr: number) : IRef
}
const FILES: Array <TF> = 'abcdefgh'.split('')
const RANKS: Array <TR> = '12345678'.split('')
const all: {
[key:string] : IRef
} = {}
const compute = function(fidx: number, ridx: number): IRef {
const file: TF = FILES[fidx]
const rank: TR = RANKS[ridx]
return all[file + rank]
}
const select = function(key: string) : IRef {
return all[key]
}
const get = function(arg1: string | number, arg2: ?number) : IRef {
if(arguments.length === 1) {
return select (arg1)
}
if(arguments.length === 2) {
return compute (arg1, arg2)
}
}
const each = function (callback) {
Object.keys(all).forEach((key, idx) => {
callback.call(this, all[key], idx)
})
}
class Ref implements IRef {
constructor (refdef: TRefDef) {
this.file = refdef.file
this.fidx = refdef.fidx
this.rank = refdef.rank
this.ridx = refdef.ridx
this.key = this.file + this.rank
}
toString() : string {
return 'Ref: ' + '(' + this.fidx + ',' + this.ridx + ')' + ' ' + this.file + this.rank
}
move (df: number, dr: number) : Ref {
let f = FILES.indexOf(fidx)
let r = RANKS.indexOf(ridx)
f += df
r += dr
return all[FILES[f] + RANKS[r]]
}
}
FILES.forEach((file, fidx) => {
RANKS.forEach( (rank, ridx) => {
const key: string = file + rank
const ref: Ref = new Ref({ file, fidx, rank, ridx })
all[key] = ref
})
})
Ref.empty = new Ref('', -1, '', -1)
const Refs = { compute, select, each, get }
// let f = { compute, each, selection }
// console.log(f)
// export { compute, each, select, Ref }
export { Refs, Ref }

How to turn nested plain js objects into Ember.js objects?

If I have a nested set of plain old javascript objects (for example, having been returned from JSON), how do I them into Ember.js objects (or at least getting the binding functionality working)?
For example, if I have an object like:
var x = {
bar: {
baz: "quux"
}
}
Then I turn that into an Ember object:
var y = Ember.Object.create(x);
Then setting the value of "baz" won't update any views I have, because it is just a normal js object, not an Ember object.
I know I can just recursively go over the object keys, and do Ember.Object.create all the way down, but is there an alternative approach?
I'm not sure how you're attempting to set the value of baz, after you've created the Ember.Object, but you should make sure you use an observer-aware setter function. For this example, I'd suggest using setPath().
For example:
var x = {
bar: {
baz: "quux"
}
};
var y = Ember.Object.create(x);
y.setPath('bar.baz', 'foo');
jsFiddle example, showing a view update after setting: http://jsfiddle.net/ebryn/kv3cU/
Here's my version:
import { typeOf } from '#ember/utils'
import EmberObject from '#ember/object'
export default function deepEmberObject(anything) {
if (typeOf(anything) === 'array') {
return anything.map(a => deepEmberObject(a))
} else if (typeOf(anything) === 'object') {
let converted = Object.keys(anything).reduce((acc, k) => {
acc[k] = deepEmberObject(anything[k])
return acc
}, {})
return EmberObject.create(converted)
} else {
return anything
}
}
test:
import deepEmberObject from 'zipbooks/utils/deep-ember-object'
import { module, test } from 'qunit'
module('Unit | Utility | deep-ember-object', function() {
test('it works', function(assert) {
let result = deepEmberObject({ pandas: [{ id: 3, children: [{ name: 'Bobby', features: { weight: 3 } }] }] })
assert.equal(
result
.get('pandas')[0]
.get('children')[0]
.get('features')
.get('weight'),
3
)
})
})
For some reason I had to define nested objects independently in order to ensure the computed works properly (even the enumerated ones).
For that I end up crafting these 2 utility functions:
import EmberObject from '#ember/object';
import { A } from '#ember/array';
function fromArrayToEmberArray(array) {
const emberArray = A();
array.forEach(function(item) {
if (Array.isArray(item)) {
emberArray.push(fromArrayToEmberArray(item));
} else if (item && typeof item === 'object') {
emberArray.push(fromObjectToEmberObject(item));
} else {
emberArray.push(item);
}
});
return emberArray;
}
function fromObjectToEmberObject(pojo) {
const emberObject = EmberObject.create();
for (const key in pojo) {
const keyObject = pojo[key];
if (Array.isArray(keyObject)) {
emberObject.set(key, fromArrayToEmberArray(keyObject))
} else if (keyObject && typeof keyObject === 'object') {
emberObject.set(key, fromObjectToEmberObject(keyObject))
} else {
emberObject.set(key, keyObject);
}
}
return emberObject;
}
export default {fromObjectToEmberObject};

Categories