webpack js compiler issues with spread syntax from npm package - javascript

I have the following class:
class AnalyticsService {
/** Log an analytics event. */
log(options) {
return Promise.all(this.logGoogleAnalytics(options), this.logKenticoAnalytics(options));
}
/** Log an analytics event to GA. */
logGoogleAnalytics(options) {
if (!options || !window.ga) {
console.warn('Analytics: Failed to log event:', options);
return Promise.reject(false);
}
const { category, action, label, value } = options;
ga('send', 'event', category, action, label, value);
return Promise.resolve(true);
}
/** Log an analytics event to Kentico. */
logKenticoAnalytics(options) {
if (!options || !window.ga) {
console.warn('Analytics: Failed to log activity:', options);
return Promise.reject(false);
}
const data = {
...options,
referrer: options.referrer || document.referrer,
url: options.url || location.href,
};
return postJSON(`${URL_VIRTUAL_PATH}/activities`, data).then(
response => {
if (!response.ok) {
console.warn('Analytics: Failed to log activity:', response, options);
return Promise.reject(false);
}
return response.json();
},
error => {
console.warn('Analytics: Failed to log activity:', error, options);
return Promise.reject(false);
},
);
}
}
Which if I include in another js file with
import AnalyticsService from './AnalyticsService';
Will compile and work well. However we are trying to get reuse out of our js by exporting it to npm so we can npm it into different projects.
This has all worked well but now if I use
import AnalyticsService from '#jsrepo/analyticsservice/AnalyticsService';
I get a compile error for the spread syntax:
ERROR in ./~/#jsrepo/analyticsservice/AnalyticsService.js
Module parse failed: C:\Web\SiteFiles\src\node_modules\#jsrepo\analyticsservice\AnalyticsService.js Unexpected token (30:6)
You may need an appropriate loader to handle this file type.
|
| const data = {
| ...options,
| referrer: options.referrer || document.referrer,
| url: options.url || location.href,
# ./js/components/init-analytics.js 7:24-79
# ./js/components/index.js
# ./js/main.js
# multi webpack/hot/dev-server webpack-hot-middleware/client?reload=true sass/main.scss js/main
I thought it may be a dependency issue, so I have added
"babel-plugin-transform-object-rest-spread": "^6.23.0"
to the dependencies of the npm package and also added it to the options of the babel loader in webpack config:
use: {
// Use the babel-loader to transpile the JS to browser-compatible syntax.
loader: 'babel-loader',
options: {
plugins: [require('babel-plugin-transform-object-rest-spread')]
// have also tried adding babel-plugin-transform-es2015-spread
}
},
But I am unable to remove the error. Does anyone know how to get the spread syntax to work when importing an npm package or how to edit it so I don't need it - mainly I don't understand this line:
const { category, action, label, value } = options;
in order to assign whatever options is to the data without using the ...

Figured out that
const { category, action, label, value } = options;
Was called object deconstructing. From that I was able to determine what was in the options so just added those to the data var separately in order to get rid of the ...:
const data = {
category: options.category,
action: options.action,
label: options.label,
value: options.value,
referrer: options.referrer || document.referrer,
url: options.url || location.href,
};
If anyone can answer how to get the js loader to work properly, then I will not accept this answer and will accept theirs as it would be better to use the shorthand

Related

Getting fatal webpack 5 error while trying to make an API request

So I am currently trying to make an application that can get a random word from a random word API (which i have working), and then uses that word to find an image related to it from the Microsoft Bing image search API.
I've been running tests with their official documentation on how to make a request to no avail. I keep getting this error
ERROR in ./src/utils/daily-entry.js 3:12-28
Module not found: Error: Can't resolve 'https' in 'C:\Users\bottl\Desktop\Personal Projects\critique\client\src\utils'
BREAKING CHANGE: webpack < 5 used to include polyfills for node.js core modules by default.
This is no longer the case. Verify if you need this module and configure a polyfill for it.
If you want to include a polyfill, you need to:
- add a fallback 'resolve.fallback: { "https": require.resolve("https-browserify") }'
- install 'https-browserify'
If you don't want to include a polyfill, you can use an empty module like this:
resolve.fallback: { "https": false }
This error seems to be telling me it's an issue with me using Webpack 5. I don't know too much about webpack but pretty sure I am not using it, and Webpack isn't referenced anywhere in my code.
Here is what the request code looks like
let https = require('https');
export const getWord = () => fetch('https://random-word-form.herokuapp.com/random/noun');
export const getImage = () => {
let subscriptionKey = 'I Put my API key here';
let host = 'api.bing.microsoft.com';
let path = '/v7.0/images/search';
let term = 'tropical ocean';
let request_params = {
method: 'GET',
hostname: host,
path : path + '?q=' + encodeURIComponent(term),
headers : {
'Ocp-Apim-Subscription-Key' : subscriptionKey,
}
}
let response_handler = function (response) {
let body = '';
response.on('data', function(d) {
body += d;
});
response.on('end', function() {
let firstImageResult = imageResults.value[0];
console.log(`Image result count: ${imageResults.value.length}`);
console.log(`First image thumbnail url: ${firstImageResult.thumbnailUrl}`);
console.log(`First image web search url: ${firstImageResult.webSearchUrl}`);
})
}
let req = https.request(request_params, response_handler);
req.end;
}
The webpack error is the only error code I'm getting on render.

programmically give input to yeoman cmd for CI/CD tool

when we run the yeoman it is asking to add input one by one, is there anyway to give all input one go ? or programically?
for Example :
yo azuresfguest
it is asking to add 5 inputs, which i want to give one time, so i can run in CI/CD system
Thanks,
There isn't a general way, unfortunately. The specific generator would need to allow it.
I think it is deserving of a feature request on the Yeoman project, which I've logged here.
As a cumbersome workaround, you can create your own generator which re-uses an existing generator. The TypeScript code below gives an example; I'm using this approach to automate my CI process.
Add option to constructor:
constructor(args: string, opts: Generator.GeneratorOptions) {
super(args, opts);
...
this.option("prompts-json-file", {
type: String,
default: undefined,
description: "Skips prompting; uses file contents. Useful for automation",
});
}
Use the option:
async prompting() {
if (this.options["prompts-json-file"] !== undefined) {
this.answers = new Answers(JSON.parse(
fs.readFileSync(this.options["prompts-json-file"]).toString()
));
}
else {
this.answers = ...
}
}
Unfortunately this does bypass prompt validation so you'd need to separately ensure your file contains valid values.
Using it is relatively simple:
yo my-generator --prompts-json-file ./prompts.json
This should be accomplished using Yeomans storage API and a .yo-rc.json file:
https://yeoman.io/authoring/storage.html
I used to use a self defined option to make that optional similar to the approach from #BjornO
constructor(args, opts) {
super(args, opts);
// This method adds support for a `--yo-rc` flag
this.option('yo-rc', {
desc: 'Read and apply options from .yo-rc.json and skip prompting',
type: Boolean,
defaults: false
});
}
initializing() {
this.skipPrompts = false;
if (this.options['yo-rc']) {
const config = this.config.getAll();
this.log(
'Read and applied the following config from ' +
chalk.yellow('.yo-rc.json:\n')
);
this.log(config);
this.log('\n');
this.templateProps = {
projectName: config.projectName,
};
this.skipPrompts = true;
}
}
prompting() {
if (!this.skipPrompts) {
// Have Yeoman greet the user.
this.log(
yosay(
`Yo, welcome to the ${superb()} ${chalk.yellow(
'Baumeister'
)} generator!`
)
);
const prompts = [
{
type: 'input',
name: 'projectName',
message: 'What’s the name of your project?',
// Default to current folder name
default: _s.titleize(this.appname)
}
];
return this.prompt(prompts).then(props => {
this.templateProps = {
projectName: props.projectName
};
});
}
}
See https://github.com/micromata/generator-baumeister/blob/master/app/index.js#L20-L69 for the whole generator code and https://github.com/micromata/generator-baumeister/blob/master/\_\_tests__/yo-rc.json for the corresponding .yo-rc.json file.

How to fix the Error "TypeError: cy.[custom command] is not a function"?

I have written some function in commands.js file for cypress automation testing, out of which I am able to invoke only one i.e."login" but unable to invoke other functions form another .js file. Cypress Test Runner showing
"TypeError: cy.FillAddCaseDetails is not a function"
describe('Adding a Case on CSS Poratal ', function() {
before(function () {
cy.login() // calling login function successfully
})
it('open add case',function(){
cy.wait(9000)
cy.hash().should('contains','#/home')
cy.wait(['#GETcontentLoad']);
cy.wait(['#POSTcontentLoad']);
cy.get('[uib-tooltip="Add Case"]').click({force:true})
cy.log('clicked on Add case')
cy.wait(3000)
cy.get('[ng-click="lookup.cancel()"]').click({force: true})
cy.get('[ng-click="lookup.closeAddCase()"]').click({force: true})
cy.get('[uib-tooltip="Add Case"]').click({force:true})
cy.wait(3000)
cy.get('[ng-model="lookup.selectedPartner"]',{force:true})
.type(AddJob.JobData.Partner,{force: true})
cy.xpath('//input[#ng-model="lookup.selectedPartner"]')
.should('be.visible').then(() => {
cy.FillAddCaseDetails() // unable to call
cy.FillCustomerDetails() // unable to call
})
Function:
Cypress.Commands.add("FillCustomerDetails", () => {
cy.get('[ng-model="lookup.firstName"]')
.type(AddJob.JobData.FirstName, { force: true})
cy.get('[ng-model="lookup.lastName"]')
.type(AddJob.JobData.LastName, { force: true })
cy.get('[ng-model="lookup.customerPhone"]')
.type(AddJob.JobData.CustomerPhone, { force: true })
cy.get('[value="NEXT"]').click({ force: true })
})
expected : function will get called
actual : TypeError: cy.FillAddCaseDetails is not a function
This is the top result for this error so I would like to add what I did to fix it. This is relevant to version >=10 and typescript. The problem ended up being that the supportFile setting in cypress.config.ts was set to false; I changed my config to this:
import cypress, { defineConfig } from 'cypress'
export default defineConfig({
e2e: {
'baseUrl': 'http://localhost:4200',
supportFile: 'cypress/support/e2e.ts'
},
})
I created the custom commands in commands.ts
declare namespace Cypress {
interface Chainable<Subject = any> {
/**
* Custom command to select DOM element by data-cy attribute.
* #example cy.dataCy('greeting')
*/
clearIndexedDB(): Promise<void>
}
}
Cypress.Commands.add('clearIndexedDB', async () => {
const databases = await window.indexedDB.databases();
await Promise.all(
databases.map(
({ name }) => {
if (!name) return
return new Promise((resolve, reject) => {
const request = window.indexedDB.deleteDatabase(name);
request.addEventListener('success', resolve);
request.addEventListener('blocked', resolve);
request.addEventListener('error', reject);
})
},
),
);
});
Then I uncommented this line in my e2e.ts file
import './commands';
In my case solution was a restart of the cypress test runner.
If you added your Custom Command to support/commands.js file, You need to import that file from support/index.js file. Create support/index.js, if it's not available and add the line import "./commands.js" to it.
From the Cypress docs: https://on.cypress.io/typescript#Types-for-custom-commands
if you add the command cy.dataCy into your supportFile like this:
// cypress/support/index.js
Cypress.Commands.add('dataCy', (value) => {
return cy.get(`[data-cy=${value}]`)
})
Then you can add the dataCy command to the global Cypress Chainable interface (so called because commands are chained together) by creating a new TypeScript definitions file beside your supportFile, in this case at cypress/support/index.d.ts.
// in cypress/support/index.d.ts
// load type definitions that come with Cypress module
/// <reference types="cypress" />
declare namespace Cypress {
interface Chainable {
/**
* Custom command to select DOM element by data-cy attribute.
* #example cy.dataCy('greeting')
*/
dataCy(value: string): Chainable<Element>
}
}
cy.xpath("//div[#class='c-navigatorItem-faceplate ng-scope ng-isolate-scope']").click();
Is it a valid to use because I am getting the TypeError cy.xpath is not a function

API client in Javascript

I need a little help to solve a problem in my project.
Scenario:
First: I have a SPA web site that is being developed in Vue.js.
Second: I also have a Web API spec in Swagger that I want to use to generate my client code in Javascript.
Lastly: I'm using swagger-codegen-cli.jar for that.
What I've done until now
1 - Download the last swagger-codegen-cli.jar stable version with javascript support:
curl http://central.maven.org/maven2/io/swagger/swagger-codegen-cli/2.4.7/swagger-codegen-cli-2.4.7.jar -o swagger-codegen-cli.jar
2 - Generate the client code using:
java -jar swagger-codegen-cli.jar generate -i http://192.168.0.85:32839/api/swagger/v1/swagger.json -l javascript -o ./web_api_client/
3 - Add the generated module to my project:
"dependencies": {
// ...
"vue": "^2.6.10",
"vue-router": "^3.0.3",
"web_api_client": "file:./web_api_client"
},
4 - Execute npm install. Apparently, it's working fine.
5 - At this moment I faced the problem. For some reason, the module generated isn't loaded completely.
export default {
name: 'home',
components: {
HelloWorld
},
mounted() {
var WebApiClient = require("web_api_client");
var defaultClient = WebApiClient.ApiClient.instance;
var oauth2 = defaultClient.authentications["oauth2"];
oauth2.accessToken = "YOUR ACCESS TOKEN";
var apiInstance = new WebApiClient.VersaoApi();
var callback = function(error, data, response) {
if (error) {
console.error(error);
} else {
console.log('API called successfully. Returned data: ' + data);
}
};
apiInstance.apiVersaoGet(callback);
}
}
6 - The line var WebApiClient = require("web_api_client"); is working without any error, however, not working 100%. The instance of the module has been created but empty. For instance, WebApiClient.ApiClient is always undefined.
7 - I took a look at the generated code and I think the problem is related with the way the module is being loaded.
(function(factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['ApiClient', 'api/VersaoApi'], factory);
} else if (typeof module === 'object' && module.exports) {
// CommonJS-like environments that support module.exports, like Node.
module.exports = factory(require('./ApiClient'), require('./api/VersaoApi'));
}
}(function(ApiClient, VersaoApi) {
'use strict';
// ...
In this code, neither of ifs blocks are executed.
Has someone faced a problem like that?
Some advice?
Many thanks, folks.
Solution
After a while trying to fix the problem with require("web_api_client"); I decided to use ES6 instead ES5.
I found an option in swagger-codegen-cli.jar to generate the client code using ES6 as shown below:
java -jar swagger-codegen-cli.jar generate -i http://192.168.0.85:32839/api/swagger/v1/swagger.json -l javascript --additional-properties useES6=true -o ./web_api_client/
Using ES6 I was able to import the javascript module direct from the generated source as shown in the code below.
import WebApiClient from "./web_api_client/src/index";
let defaultClient = WebApiClient.ApiClient.instance;
defaultClient.basePath = 'http://192.168.0.85:32839';
// Configure OAuth2 access token for authorization: oauth2
let oauth2 = defaultClient.authentications["oauth2"];
oauth2.accessToken = "YOUR ACCESS TOKEN";
let apiInstance = new WebApiClient.VersaoApi();
apiInstance.apiVersaoGet((error, data, response) => {
if (error) {
console.error(error);
} else {
console.log("API called successfully. Returned data: " + data + response);
}
});
When I first ran the code I got an error because the module WebApiClient generated didn't have the keyword default in the export block.
Original generated code
export {
/**
* The ApiClient constructor.
* #property {module:ApiClient}
*/
ApiClient,
// ...
Alter changed
export default {
/**
* The ApiClient constructor.
* #property {module:ApiClient}
*/
ApiClient,
// ...
Now everything is working fine.

How to add authorization header when runtime import webpack chunks of Vue components

The purpose of this task is to make it impossible to download the Vue-component package (*.js file) knowing the address of the component, but not having an access token.
I'm developing an access control system and a user interface in which the set of available components depends on the user's access level.
The system uses the JSON API and JWT authorization. For this, Axios is used on the client side. To build the application, we use Webpack 4, to load the components, we use the vue-loader.
After the user is authorized, the application requests an array of available routes and metadata from the server, then a dynamically constructed menu and routes are added to the VueRouter object.
Below I gave a simplified code.
import axios from 'axios'
import router from 'router'
let API = axios.create({
baseURL: '/api/v1/',
headers: {
Authorization: 'Bearer mySecretToken12345'
}
})
let buildRoutesRecursive = jsonRoutes => {
let routes = []
jsonRoutes.forEach(r => {
let path = r.path.slice(1)
let route = {
path: r.path,
component: () => import(/* webpackChunkName: "restricted/[request]" */ 'views/restricted/' + path)
//example path: 'dashboard/users.vue', 'dashboard/reports.vue', etc...
}
if (r.children)
route.children = buildRoutesRecursive(r.children)
routes.push(route)
})
return routes
}
API.get('user/routes').then(
response => {
/*
response.data =
[{
"path": "/dashboard",
"icon": "fas fa-sliders-h",
"children": [{
"path": "/dashboard/users",
"icon": "fa fa-users",
}, {
"path": "/dashboard/reports",
"icon": "fa fa-indent"
}
]
}
]
*/
let vueRoutes = buildRoutesRecursive(response.data)
router.addRoutes(vueRoutes)
},
error => console.log(error)
)
The problem I'm having is because Webpack loads the components, by adding the 'script' element, and not through the AJAX request. Therefore, I do not know how to add an authorization header to this download. As a result, any user who does not have a token can download the code of the private component by simply inserting his address into the navigation bar of the browser.
Ideally, I would like to know how to import a vue component using Axios.
Or, how to add an authorization header to an HTTP request.
I needed something similar and came up with the following solution. First, we introduce a webpack plugin that gives us access to the script element before it's added to the DOM. Then we can munge the element to use fetch() to get the script source, and you can craft the fetch as needed (e.g. add request headers).
In webpack.config.js:
/*
* This plugin will call dynamicImportScriptHook() just before
* the script element is added to the DOM. The script object is
* passed to dynamicImportScriptHook(), and it should return
* the script object or a replacement.
*/
class DynamicImportScriptHookPlugin {
apply(compiler) {
compiler.hooks.compilation.tap(
"DynamicImportScriptHookPlugin", (compilation) =>
compilation.mainTemplate.hooks.jsonpScript.tap(
"DynamicImportScriptHookPlugin", (source) => [
source,
"if (typeof dynamicImportScriptHook === 'function') {",
" script = dynamicImportScriptHook(script);",
"}"
].join("\n")
)
);
}
}
/* now add the plugin to the existing config: */
module.exports = {
...
plugins: [
new DynamicImportScriptHookPlugin()
]
}
Now, somewhere convenient in your application js:
/*
* With the above plugin, this function will get called just
* before the script element is added to the DOM. It is passed
* the script element object and should return either the same
* script element object or a replacement (which is what we do
* here).
*/
window.dynamicImportScriptHook = (script) => {
const {onerror, onload} = script;
var emptyScript = document.createElement('script');
/*
* Here is the fetch(). You can control the fetch as needed,
* add request headers, etc. We wrap webpack's original
* onerror and onload handlers so that we can clean up the
* object URL.
*
* Note that you'll probably want to handle errors from fetch()
* in some way (invoke webpack's onerror or some such).
*/
fetch(script.src)
.then(response => response.blob())
.then(blob => {
script.src = URL.createObjectURL(blob);
script.onerror = (event) => {
URL.revokeObjectURL(script.src);
onerror(event);
};
script.onload = (event) => {
URL.revokeObjectURL(script.src);
onload(event);
};
emptyScript.remove();
document.head.appendChild(script);
});
/* Here we return an empty script element back to webpack.
* webpack will add this to document.head immediately. We
* can't let webpack add the real script object because the
* fetch isn't done yet. We add it ourselves above after
* the fetch is done.
*/
return emptyScript;
};
Although sspiff's answer looks quite promising, it did not work directly for me.
After some investigation this was mainly due to me using Vue CLI 3 and thus a newer version of webpack. (which is kinda weird as sspiff mentioned using webpack 4.16.1).
Anyway to solve it I used the following source: medium.com,
Which gave me the knowledge to edit the given code.
This new code is situated in vue.config.js file:
/*
* This plugin will call dynamicImportScriptHook() just before
* the script element is added to the DOM. The script object is
* passed to dynamicImportScriptHook(), and it should return
* the script object or a replacement.
*/
class DynamicImportScriptHookPlugin {
apply(compiler) {
compiler.hooks.compilation.tap(
"DynamicImportScriptHookPlugin", (compilation) =>
compilation.mainTemplate.hooks.render.tap(
{
name: "DynamicImportScriptHookPlugin",
stage: Infinity
},
rawSource => {
const sourceString = rawSource.source()
if (!sourceString.includes('jsonpScriptSrc')) {
return sourceString;
} else {
const sourceArray = sourceString.split('script.src = jsonpScriptSrc(chunkId);')
const newArray = [
sourceArray[0],
'script.src = jsonpScriptSrc(chunkId);',
"\n\nif (typeof dynamicImportScriptHook === 'function') {\n",
" script = dynamicImportScriptHook(script);\n",
"}\n",
sourceArray[1]
]
return newArray.join("")
}
}
)
);
}
}
module.exports = {
chainWebpack: (config) => {
config.plugins.delete('prefetch')
},
configureWebpack: {
plugins: [
new DynamicImportScriptHookPlugin()
]
}
}
The second piece of code provided by sspiff has stayed the same and can be placed in the App.vue file or the index.html between script tags.
Also to further improve this answer I will now explain how to split the chunks in Vue CLI 3 for this specific purpose.
as you can see I also added the chainWebpack field to the config. This makes sure that webpack does not add prefetch tags in the index.html. (e.g. it will now only load lazy chunks when they are needed)
To further improve your splitting I suggest changing all your imports to something like:
component: () => import(/* webpackChunkName: "public/componentName" */ /* webpackPrefetch: true */'#/components/yourpubliccomponent')
component: () => import(/* webpackChunkName: "private/componentName" */ /* webpackPrefetch: false */'#/components/yourprivatecomponent')
This will make sure that all your private chunks end up in a private folder and that they will not get prefetched.
The public chunks will end up in a public folder and will get prefetched.
For more information use the following source how-to-make-lazy-loading-actually-work-in-vue-cli-3
Hope this helps anyone with this problem!
To perform a simple component download using an access token, you can do the following...
1) Use asynchronous component loading with file extraction. Use webpackChunkName option to separate file or directory/file, like:
components: {
ProtectedComp: () => import(/* webpackChunkName: "someFolder/someName" */ './components/protected/componentA.vue')
}
2) Configure server redirection for protected files or direcory. Apache htaccess config for example:
RewriteRule ^js/protected/(.+)$ /js-provider.php?r=$1 [L]
3) write a server-side script that checks the token in the header or cookies and gives either the contents of .js or 403 error.

Categories

Resources