Is there a way to lint .html files in Angular using a custom EsLint plugin? - html

I would like to create a custom linting rule for HTML files inside and Angular2+ project that warns me when there is a clickable element without an ID set. By clickable element I mean and other custom Angular components which will have a click event.
The project is currently using EsLint and I am trying to create a custom plugin which would do the job.
Right now I tried to run this rule from a plugin that enforces to have a class called "btn" at least. But linting does trigger any warning, more than that, it seems like it does not lint .html files at all.
module.exports = {
meta: {
docs: {
description: '',
category: 'Possible Errors',
recommended: true,
},
fixable: null,
},
create: function (context) {
return {
JSXOpeningElement: (node) => {
const nodeType = node.name.name;
if (nodeType !== 'button') {
return;
}
const legalClassNameAttributes = node.attributes.filter((attr) => {
const isClassName = attr.type === 'JSXAttribute' && attr.name.name === 'className';
return isClassName && (attr.value.type !== 'Literal' ||
attr.value.value.includes('btn'));
});
if (!legalClassNameAttributes.length) {
context.report({
message:
'Use correct class',
node,
});
}
},
};
},
};

Related

Chrome Extension embedding script in active web page, in MV3?

beautiful people on the internet. I am new to chrome extension not new to writing code though. I have implemented webpack to use external packages. One major one in my application is npm package by the name "mark.js".
My application works like this i want some specific words to be highlighted in active webpage using this package. I have written code for this to achieve the functionality but the problem is with loading the script in a web page. I have performed different aspect of loading script but that doesnot work. The new MV3 version have some strict rules.
I want to achieve anything similar of loading script in an active webpage. Please help.
btn.addEventListener("click", async () => {
console.log("BUTTON IS PRESSED!!");
try {
await chrome.tabs.query(
{ active: true, currentWindow: true },
async function (tabs) {
chrome.scripting.executeScript({
target: { tabId: tabs[0].id },
func: highlightText,
args: [arr],
});
}
);
} catch (e) {
console.log("ERROR AT CHROME TAB QUERY : ", e);
}
});
async function highlightText(arr) {
console.log(typeof Mark);
try {
var instance2 = new Mark(document.querySelector("body"));
// instance2.mark("is");
var success = [];
// const instance2 = new Mark(document.querySelector("body"));
await Promise.all(
arr.map(async function (obj) {
console.log("OBJECT TEXT : ", obj.text);
instance2.mark(obj.text, {
element: "span",
each: function (ele) {
console.log("STYLING : ");
ele.setAttribute("style", `background-color: ${obj.color};`);
if (obj.title && obj.title != "") {
ele.setAttribute("title", obj.title);
}
ele.innerHTML = obj.text;
success.push({
status: "Success",
text: obj.text,
});
},
});
})
);
console.log("SUCCESS : ", success);
} catch (error) {
console.log(error);
}
}
There's no need to use chrome.runtime.getURL. Since you use executeScript to run your code all you need is to inject mark.js before injecting the function.
Also, don't load popup.js in content_scripts, it's not a content script (these run in web pages), it's a script for your extension page. Actually, you don't need content_scripts at all.
btn.addEventListener('click', async () => {
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
const target = { tabId: tab.id };
const exec = v => (await chrome.scripting.executeScript({ target, ...v }))[0].result;
if (!await exec({ func: () => !!window.Mark })) {
await exec({files: ['mark.js.min'] });
await exec({func: highlightText, args: [arr] });
}
});
For V3 I assume you will want to use Content Scripts in your manifest to inject the javascript into every webpage it matches. I recently open-sourced TorpedoRead and had to do both V2 and V3, I recommend checking the repo as it sounds like I did something similar to you (Firefox is V2, Chrome is V3).
The code below need to be added to your manifest.json and this will execute on every page based on the matches property. You can read more about content scripts here: https://developer.chrome.com/docs/extensions/mv3/content_scripts/
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["yourscript.js"]
}
],

How to mock Forge Viewer in React Unit Tests

we're currently trying to unit / integration test our react application, which uses the forge viewer cdn script.
the to be tested code assumes that Autodesk is available on the window object, which is the case in the application (via script tags), but not in the context of testing. this leads to errors like these:
Test suite failed to run
ReferenceError: Autodesk is not defined
> 1 | export class ExtendedGuiViewer3D extends Autodesk.Viewing.GuiViewer3D {
according to the license comments, the forge viewer script only allows using it through the Autodesk servers, so I cant just download it, and require the file locally.
has anyone successfully tested components that use the forge viewer scripts?
Intro
Disclaimer, I've only recently been experimenting with "Reactifying" the Autodesk Forge Viewer!
I currently believe the 'correct' way to consume the forge viewer css / js is to pull code from the Autodesk hosted cdn. The types are still available on npm though.
For example, the endpoints for v7.52.0:
js https://developer.api.autodesk.com/modelderivative/v2/viewers/7.52.0/viewer3D.min.js
css https://developer.api.autodesk.com/modelderivative/v2/viewers/7.52.0/style.min.css
Steps
1. Add type information from npm
Firstly, if you are using typescript, you can still install the viewer types from npm with:
yarn add -D #types/forge-viewer (check/add specific version to match the version of the script from the cdn - you can verify in your package.json)
2. Sample ViewingContext.tsx
In your React code you may wish to create a React Context to manage the the script downloading for you. This example is based on next.js:
import React, { PropsWithChildren, useEffect, useState } from "react";
import Script from "next/script";
export const ViewingContext = React.createContext({
initialized: false,
});
export interface ViewingContextProps {
options: Autodesk.Viewing.InitializerOptions;
}
// Place a single global ViewingContextProvider component around the common root of all your Autodesk.Viewing (LMV) components.
// https://forge.autodesk.com/en/docs/viewer/v7/developers_guide/overview/
export const ViewingContextProvider = ({
options,
children,
}: PropsWithChildren<ViewingContextProps>): JSX.Element => {
const [scriptLoaded, setScriptLoaded] = useState(
typeof window !== "undefined" &&
window.Autodesk?.Viewing?.Initializer !== undefined
);
const [initialized, setInitialized] = useState(false);
useEffect(() => {
if (scriptLoaded && !initialized) {
Autodesk.Viewing.Initializer(options, () => setInitialized(true));
}
}, [options, initialized, scriptLoaded]);
return (
<ViewingContext.Provider value={{ initialized }}>
<link
rel="stylesheet"
href="https://developer.api.autodesk.com/modelderivative/v2/viewers/7.52.0/style.min.css"
type="text/css"
/>
<Script
async
src="https://developer.api.autodesk.com/modelderivative/v2/viewers/7.52.0/viewer3D.min.js"
onLoad={(): void => setScriptLoaded(true)}
/>
{children}
</ViewingContext.Provider>
);
};
3. Sample Viewer.tsx Component
Only mount this component as a child of the ViewingContext. You can also modify/replace this component with the ExtendedGuiViewer3D you mentioned.
import React, { useContext, useEffect, useRef } from "react";
import { ViewingContext } from "./ViewingContext";
export interface ViewerProps {
config?: Autodesk.Viewing.Viewer3DConfig;
onLoaded?: (viewer: Autodesk.Viewing.GuiViewer3D) => void;
onError?: (code: number) => void;
}
// Thin wrapper around https://forge.autodesk.com/en/docs/viewer/v7/developers_guide/overview/
// Add your own imperative hook code after GuiViewer object is loaded with the onLoaded callback.
// Place inside a relative layout div.
export const Viewer = ({
config,
onLoaded,
onError,
}: ViewerProps): JSX.Element => {
const { initialized: viewingContextInitialized } = useContext(ViewingContext);
const viewerDivRef = useRef<HTMLDivElement>(null);
const viewer = useRef<Autodesk.Viewing.GuiViewer3D>();
// Viewer imperative loading code
useEffect(() => {
if (viewingContextInitialized && !viewer.current && viewerDivRef.current) {
viewer.current = new Autodesk.Viewing.GuiViewer3D(
viewerDivRef.current,
config
);
const startedCode = viewer.current.start();
if (startedCode > 0) {
onError && onError(startedCode);
return;
}
if (onLoaded) onLoaded(viewer.current);
}
}, [config, onLoaded, onError, viewingContextInitialized]);
// Viewer destructor
useEffect(() => {
return (): void => {
if (viewer.current) {
viewer.current.finish();
}
};
}, []);
return (
<div
style={{
position: "absolute",
width: "100%",
height: "100%",
overflow: "hidden",
}}
>
<div
style={{
margin: 0,
width: "100%",
height: "100%",
}}
ref={viewerDivRef}
/>
</div>
);
};
Hope this answers your question!
so after months of fighting, these are the two options I've come up with so far.
option 1: dirty mock everything
there's a few #ts-ignore, because I dont want to mock out the whole package. i'll only mock the parts my application uses.
you could to type assertion like global.THREE = {...} as unknown as typeof THREE too. whatever floats your boat.
// setupTests.ts
// NOP_VIEWER global is not part of the #types declaration, so we need to tell typescript that there will be a global
declare global {
namespace NodeJS {
interface Global {
NOP_VIEWER: ExtendedGuiViewer3DTypes;
}
}
}
global.Autodesk = {
// #ts-ignore
Viewing: {
GuiViewer3D: jest.fn(),
Extension: jest.fn(),
ToolInterface: jest.fn(),
},
};
// #ts-ignore
global.THREE = {
Color: jest.fn(),
Vector4: jest.fn(),
};
global.NOP_VIEWER = {
disableSelection: jest.fn(),
resize: jest.fn(),
// #ts-ignore
model: {
getExternalIdMapping: (successCallback: any, _: any) => {
successCallback({ 'test-guid': 1 });
},
},
clearThemingColors: jest.fn(),
setThemingColor: jest.fn(),
isLoadDone: () => true,
isolate: jest.fn(),
};
option 2: download and require
As Autodesk Developer Adam Nagy pointed out, you probably wont get sent to jail, if you download the script file and require it locally for your tests only. (note that this is just a "probably")
keep in mind that even if you require the file, you still have to mock NOP_VIEWER as this global is only available after initializing the viewer (which you dont want to do in your tests)
// setupTests.ts
// replace the mocks of `Autodesk` and `THREE` with this require.
require('./vendors/Autodesk/viewer3D.min');
in my tests i can then use the jest spies on NOP_VIEWER
expect(NOP_VIEWER.clearThemingColors).toHaveBeenCalled();

Accessing Vuex Store Before Page Load NuxtJS

Context: I am trying to get Google Maps place data via the place_id on the beforeEnter() route guard. Essentially, I want the data to load when someone enters the url exactly www.example.com/place/{place_id}. Currently, everything works directly when I use my autocomplete input and then enter the route but it does not work when I directly access the url from a fresh tab. I've been able to solve this using the beforeEnter() route guard in traditional Vue, but cannot solve for this using Nuxt. Please help!
Question: How can I access the Vuex Store before a page loads in Nuxt?
Error: Any solution I try (see below) I either end up with a blank page or the page will not load (I think it is stuck in a loop and cannot resolve the Promise).
Attempted Solutions:
Using Middleware like below:
middleware({ store, params }) {
return store.dispatch('myModule/fetchLocation', params.id)
}
Using asyncData like below:
data(){
return{
filteredLocation: {}
}
}
// snip
async asyncData({ store, params }) {
const { data } = await store.dispatch('myModule/fetchLocation', params.id)
return filteredLocation = data
}
I tried looking into fetch, but apparently you no longer have access to context
Example Code:
In one of my store modules:
/* global google */
import Vue from 'vue'
import * as VueGoogleMaps from '~/node_modules/vue2-google-maps/src/main'
Vue.use(VueGoogleMaps, {
load: {
key: process.env.VUE_APP_GMAP_KEY,
libraries: 'geometry,drawing,places'
}
})
export const state = () => ({
selectedLocation: {}
})
export const actions = {
fetchLocation({ commit }, params) {
return new Promise((resolve) => {
Vue.$gmapApiPromiseLazy().then(() => {
const request = {
placeId: params,
fields: [
'name',
'rating',
'formatted_phone_number',
'geometry',
'place_id',
'website',
'review',
'user_ratings_total',
'photo',
'vicinity',
'price_level'
]
}
const service = new google.maps.places.PlacesService(
document.createElement('div')
)
service.getDetails(request, function(place, status) {
if (status === 'OK') {
commit('SET_PLACE', place)
resolve()
}
})
})
})
}
}
export const mutations = {
SET_PLACE: (state, selection) => {
state.selectedInstructor = selection
}
}
EDIT: I already have it in a plugin named google-maps.js and in my nuxt.config.js file I have:
plugins: [
{ src: '~/plugins/google-maps.js' }
]
//
//
build: {
transpile: [/^vue2-google-maps.js($|\/)/],
extend(config, ctx) {}
}
Using Middleware is how we can access Vuex before page loads. try putting the configuration part in a custom Nuxt plugin.
Create a file in Plugins folder (you can name it global.js).
Put this
import Vue from 'vue'
import * as VueGoogleMaps from '~/node_modules/vue2-google-maps/src/main'
Vue.use(VueGoogleMaps, {
load: {
key: process.env.VUE_APP_GMAP_KEY,
libraries: 'geometry,drawing,places'
}
})
in global.js.
Then add the plugin in nuxt.config.js like this.
plugins: [
'~/plugins/global.js'
]
Also, make sure you're using underscore before 'page_id' name in your folder structure.

How do I load a WASM module in a Vue component without initializing the module every time?

I have created a Rust library of type cdylib using
cargo web build --target=wasm32-unknown-unknown
I use a modified version of the "rust-wasm-loader" NPM package to build and load the WASM file. rust-wasm-loader uses this as a way to use the Rust code:
const wasm = require('./main.rs')
wasm.initialize().then(module => {
// Use your module here
const doub = module.cwrap('doub', 'number', ['number'])
console.log(doub(21))
})
I do not want to initialize the module every time I want to use the code. How do I load the module and use it like a library?
Since the loading of WebAssembly is asynchronous and may actually take some time for large modules, you need to handle the state when the module is not loaded, and then let the rest of the application know when the WebAssembly module is loaded.
You do not say how you are handling state in your Vue application, but if you are e.g. using Vuex you can do something like this:
const doubPlugin = store => {
wasm.initialize().then(module => {
const doub = module.cwrap('doub', 'number', ['number'])
store.subscribe((mutation, state) => {
if (mutation.type === 'DOUB_REQUEST') {
store.commit('DOUB_RESULT', doub(mutation.payload))
}
})
store.commit('DOUB_READY')
})
}
const store = new Vuex.Store({
state,
mutations,
plugins: [doubPlugin]
})
I've done a similar thing in an Elm/WebAssembly application (relevant JavaScript), so if you want to see how this can be applied in practice you can check that out.
Making a wrapper JS module that performs initialization and re-exports the promise seems like the most straightforward approach.
// main.js
module.exports = require("./main.rs").initialize().then(module => {
return {
doub: module.cwrap('doub', 'number', ['number'])
};
});
Then anything can do
require("./main.js").then(api => {
console.log(api.doub(21));
});
and will always get the same module. Or alternatively you could invert the async part and do
// main.js
const api = require("./main.rs").initialize().then(module => {
return {
doub: module.cwrap('doub', 'number', ['number'])
};
});
exports.doub = async function (val) {
return (await api).doub(val);
};
Then users of your module could do
const api = require("./main.js");
api.doub(21).then(result => {
console.log(result);
});
I created a class to wrap the WebAssembly loading and created a cwrap for every function:
class mkLib {
ready = false
_mod = require("./main.rs").initialize().then(module => {
this._mod = module
this.doub = module.cwrap('doub', 'number', ['number'])
this.ready = true
})
}
export default mkLib
In the Vue component's data there is a variable for the new class and in watch I wait for a change in the ready property:
data () {
return {
mod: new mkLib,
ready: false
}
},
watch: {
'mod.ready': function () {
this.ready = true
// now this.mod.FUNC(PARAMS) can be used
console.log(this.mod.doub(20))
}
}

Vue.js dynamic component loading + hot reload

I have Laravel 5.3.21 application with Vue.js 1.0.28
I'm using hot-reload workflow with browserify-hmr plugin.
Here is the simple gulpfile.js used to achieve that:
var elixir = require('laravel-elixir');
var gutil = require('gulp-util');
require('laravel-elixir-browserify-official');
require('laravel-elixir-vueify');
// If 'gulp watch' is run
if (gutil.env._.indexOf('watch') > -1) {
// Enable watchify for faster builds
elixir.config.js.browserify.watchify.enabled = true;
// Add the browserify HMR plugin
elixir.config.js.browserify.plugins.push({
name: 'browserify-hmr',
options: {
url: 'http://1.2.3.4:2096',
hostname: '1.2.3.4',
port: 2096
}
})
}
elixir.config.js.browserify.watchify.options.poll = true;
elixir(function (mix) {
mix.copy('node_modules/datatables.net-bs/css/dataTables.bootstrap.css',
'resources/assets/css/vendor/dataTables.bootstrap.css');
mix.styles([
'vendor/dataTables.bootstrap.css'
]);
mix.sass('app.scss');
mix.browserify('app.js');
});
I need to load components dynamically from the resources/assets/js/views/ folder, so I could make my front-end code modular and based on current route name $Laravel->routeName = request()->route()->getName() in Laravel.
For example:
// Global Settings.
Route::get('admin/settings', 'Admin#settings')
->name('admin_global_settings');
Then in resources/assets/js/views/admin/admin_global_settings.js I have code to initialize Vue.js component and register it with Vue.js instance:
var FeaturedOpportunities = require( '../../components/Featured-Opportunities.vue' );
window.Vue.component('FeaturedOpportunities', FeaturedOpportunities);
That is all nice but here is the problem in resources/assets/js/app.js:
window.Vue = require('vue');
require('vue-resource');
Vue.http.interceptors.push((request, next) => {
request.headers.set('X-CSRF-TOKEN', Laravel.csrfToken);
next();
});
// Problem is here, have to keep track of all my routes and their corresponding modules:
var routes = {
'organization_invites_roles': function () {
require('./views/organization/organization_invites_roles');
},
'admin_global_settings': function () {
require('./views/admin/admin_global_settings');
},
};
// Is there a way to load it dynamically?
if (Laravel.routeName) {
if (routes[Laravel.routeName]) {
routes[Laravel.routeName]();
}
}
new Vue({
el: 'body',
components: {
},
ready() {
console.log('Vue and Vueify all set to go!');
}
});
I've found some way that could probably solve this issue: Compiling dynamically required modules with Browserify but not sure if this applicable for my case.