Webpack shows parent scope as undefined - javascript

EDIT: I have just gone through the process of switching back to browserify and am having the same problem. So no longer a webpack problem. Still need help though
I am in the process of switching from broswerify to webpack. I have created an abstraction for my ajax calls. In that file I have some private variables that I use to set URL and timeout etc. For some reason it shows these variables (and the entire 'closure') as undefined, leading to some weird bugs. This code was working perfectly fine with browserify.
This is my webpack.config.js
const webpack = require('webpack');
const path = require('path');
module.exports = {
devtool: 'source-map',
entry: path.resolve(__dirname, 'src', 'client', 'index.js'),
output: {
path: path.resolve(__dirname, 'public'),
publicPath: 'localhost:3002',
filename: 'bundle.js',
},
module: {
loaders: [
{
test: /\.js$/,
loader: 'babel',
exclude: /node_modules/,
},
{
test: /\.(png|jpg)$/,
loader: 'url-loader',
},
],
},
plugins: [
new webpack.DefinePlugin({
__API_URL__: JSON.stringify('http://localhost:3002/api'),
}),
],
};
This is my api wrapper api.js
import request from 'superagent';
import store from './store';
import { system, account } from '../core/actions';
const API_URL = __API_URL__;
const TIMEOUT = 10000;
const _pendingRequests = {};
function getJwt() {
/**
* This retrieves the JSON Web Token from local or session storage
* We simply try both so that we don't have to subscribe to the store
* and make sure some flag is constantly updated. The reducer that handles
* the successful login will place the token in the proper place.
*/
let token = localStorage.getItem('JWT_TOKEN');
if (token) {
return 'Bearer ' + token;
}
token = sessionStorage.getItem('JWT_TOKEN');
if (token) {
return 'Bearer ' + token;
}
return null;
}
function addRequest(key, pendingRequest) {
_pendingRequests[key] = pendingRequest;
}
function abortPendingRequests(key) {
if (_pendingRequests.hasOwnProperty(key)) {
_pendingRequests[key]._callback = () => {
};
_pendingRequests[key].abort();
_pendingRequests[key] = null;
}
}
function digest(resolve, reject) {
return function consume(err, res) {
if (err && err.timeout === TIMEOUT) {
return store.dispatch(system.apiTimeout());
} else if (res.status === 401) {
return store.dispatch(account.logout());
} else if (!res.ok) {
return reject(res);
} else {
if (err) {
return reject(err);
} else {
return resolve(res.body);
}
}
};
}
export function get(actionType, resource) {
// abortPendingRequests(actionType);
return new Promise((resolve, reject) => {
const jwt = getJwt();
const url = `${API_URL}${resource}`;
const requested = request
.get(url)
.timeout(TIMEOUT);
if (jwt) {
requested.set('Authorization', jwt);
}
// addRequest(actionType, requested);
requested.end(digest(resolve, reject));
});
}
export function post(actionType, resource, data) {
// abortPendingRequests(actionType);
return new Promise((resolve, reject) => {
const jwt = getJwt();
const url = `${API_URL}${resource}`;
const requested = request
.post(url)
.timeout(TIMEOUT);
if (jwt) {
requested.set('Authorization', jwt);
}
if (data) {
requested.send(data);
}
// addRequest(actionType, requested);
requested.end(digest(resolve, reject));
});
}
export function put(actionType, resource, data) {
// abortPendingRequests(actionType);
return new Promise((resolve, reject) => {
const jwt = getJwt();
const url = `${API_URL}${resource}`;
const requested = request
.put(url)
.timeout(TIMEOUT);
if (jwt) {
requested.set('Authorization', jwt);
}
if (data) {
requested.send(data);
}
requested.end(digest(resolve, reject));
});
}
export function del(actionType, resource) {
// abortPendingRequests(actionType);
return new Promise((resolve, reject) => {
const jwt = getJwt();
const url = `${API_URL}${resource}`;
const requested = request
.del(url)
.timeout(TIMEOUT);
if (jwt) {
requested.set('Authorization', jwt);
}
// addRequest(actionType, requested);
requested.end(digest(resolve, reject));
});
}
There are some weird comments that are the result of trying to debug the problem. But basically, if I set a breakpoint at const _pendingRequests = {}; it shows API_URL and TIMEOUT as being set properly. But if I set a breakpoint at const url =${API_URL}${resource}; in export function get it shows them as undefined as I will show with screenshots.
One thing I am just noticing is that it is breaking on the child scope prior to breaking on the parent scope. I am guessing that has something to do with it, but I am not sure how to change this behavior. I work in node so I have written this like I would write it for the server.
This is the file where I am importing api.js
import * as api from '../../core/api';
import { endpoints } from '../../constants';
export const FETCH_LOOKUPS = 'FETCH_LOOKUPS';
export const FETCH_LOOKUPS_SUCCESS = 'FETCH_LOOKUPS_SUCCESS';
export function fetchLookupsSuccess(lookups) {
return {
type: FETCH_LOOKUPS_SUCCESS,
lookups,
};
}
export function asyncFetchLookups() {
return dispatch => {
return api.get(FETCH_LOOKUPS, endpoints.LOOKUP)
.then(lookups => dispatch(fetchLookupsSuccess(lookups)));
};
}
export const FETCH_LANG = 'FETCH_LANG';
export const FETCH_LANG_SUCCESS = 'FETCH_LANG_SUCCESS';
export function fetchLangSuccess(language) {
return {
type: FETCH_LANG_SUCCESS,
language,
};
}
export function asyncFetchLang() {
return dispatch => {
return api.get(FETCH_LANG, endpoints.LANGUAGE)
.then(language => dispatch(fetchLangSuccess(language)));
};
}
Started digging into the transpiled code and found this
function(module, exports, __webpack_require__) {
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.TIMEOUT = exports.API_URL = undefined;
exports.get = get;
exports.post = post;
exports.put = put;
exports.del = del;
var _superagent = __webpack_require__(427);
var _superagent2 = _interopRequireDefault(_superagent);
var _store = __webpack_require__(430);
var _store2 = _interopRequireDefault(_store);
var _actions = __webpack_require__(444);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var API_URL = exports.API_URL = ("http://localhost:3002/api"); /**
* This file serves as a wrapper for any ajax requests that need to be made
* - contains a generic call for PUT, POST, GET and DELETE request
* - always attempts to append a JSON Web Token if present
* - keeps track of all pending requests and aborts identical requests
*/
var TIMEOUT = exports.TIMEOUT = 10000;
As you can see it initially sets TIMEOUT and API_URL as undefined. It then exports get, post etc and then sets TIMEOUT and API_URL but that is after the exported get is already being accessed. Not sure why it sets them to undefined or how to fix this behavior.

According to how you require (probably import) the get function babel may transpile first the get function and hand it over to node's require which then evals it.
You then don't have API_URL transpiled yet. This looks like an edge case.
Instead of using the ES6 export, just for this file, use module.exports and use node's require to import it to dismiss that kind of bug.
If this works, try importing instead of requireing (and the way around exporting instead of using module.exports) to narrow the bug.
Note: This is more a hint/workaround than a solution. You may provide the file from where you make the require, this might be useful for other people to answer you.

The problem had to do with the function being called prior the javascript finishing parsing I guess. (Still not sure why) The workaround I found was to attach my initialization function to the window object and call it in an IIFE at the body of my HTML.

Related

Javascript Unexpected reserved word error

I am playing with a client side router code from https://github.com/dcode-youtube/single-page-app-vanilla-js repo. I try to change this code in a way that i can reuse, because in the original code he hardcoded the routes in the router function.
I know(i think i know) the main reason why i get the unexpected reserved word error, i just dont know how to solve it.
Router.js
export default class trdsRouter{
constructor(routes){
this.routes = routes;
window.addEventListener("popstate", this.run);
document.body.addEventListener("click", e => {
if (e.target.matches("[trds-router-link]")) {
e.preventDefault();
this.navigateTo(e.target.href);
}
});
}
run = () => {
const potentialMatches = this.routes.map(route => {
return {
route: route,
result: location.pathname.match(pathToRegex(route.path))
};
});
let match = potentialMatches.find(potentialMatch => potentialMatch.result !== null);
if (!match) {
match = {
route: this.routes[0],
result: [location.pathname]
}
}
// THIS LINE IS THE PROBLEM
const view = new match.route.view(getParams(match));
document.querySelector("#app").innerHTML = await view.getHtml();
}
navigateTo = url => {
history.pushState(null, null, url);
this.run();
}
}
const pathToRegex = path => new RegExp("^" + path.replace(/\//g, "\\/").replace(/:\w+/g, "(.+)") + "$");
const getParams = match => {
const values = match.result.slice(1);
const keys = Array.from(match.route.path.matchAll(/:(\w+)/g)).map(result => result[1]);
return Object.fromEntries(keys.map((key, i) => {
return [key, values[i]];
}));
};
where i construct the router:
import trdsRouter from "./router.js";
import Dashboard from "./views/Dashboard.js";
import Posts from "./views/Posts.js";
import PostView from "./views/PostView.js";
import Settings from "./views/Settings.js";
let router = new trdsRouter([
{ path: "/", view: Dashboard },
{ path: "/posts", view: Posts },
{ path: "/posts/:id", view: PostView },
{ path: "/settings", view: Settings }
]);
router.run();
And then there is the abstract view class(i extend this class for the dashboard, posts, etc.):
export default class {
constructor(params) {
this.params = params;
}
setTitle(title) {
document.title = title;
}
async getHtml() {
return "";
}
}
So i think the problem is that in the trdsRouter class it does not yet know that a routes view property is a class( but i pass a class to it when i construct the router), thats why it throws error. how would i solve this? thank you.
Im sorry guys, it was a terrible mistake. The problem wasnt the new keyword highlighted in the routers code but the next line with the 'await' keyword. The routers run function wasnt declared as async so it threw the error. Changed the run function to async and it works. Thank you for your comments.

Dynamically export a module by reading all files in a directory in Node.js

So today i was trying read all default exports
from some directory which has index.js. Try to wrap it inside one object and export it back again. Is there a better way to handle this ?
export default (() => require('fs')
.readdirSync(__dirname)
.filter(fileName => !!/.js$/ig.test(fileName))
.map(fileName => fileName.split('.')[0])
.reduce((defaultExportObj, nextFileName) => {
try {
return {
...defaultExportObj,
[nextFileName]: require(__dirname + `/${nextFileName}`),
};
}catch(err) { throw err; }
}, {}))();
I guess i'd do something like this - not sure if this is better - w/e better is ^^
webpack: require.context
function expDefault(path, mode = "sync"){
const modules = {}
const context = require.context(path, false, /\.js$/, mode)
context.keys().forEach(file => {
const name = fileName.replace(/^.+\/([^/]+)\.js$/, "$1")
modules[name] = context(name).default
})
return modules
}
export default expDefault(__dirname)

How to create plugin for Nuxt.js?

This is my rpc.js plugin file:
const { createBitcoinRpc } = require('#carnesen/bitcoin-rpc')
const protocol = 'http'
const rpcuser = 'root'
const rpcpassword = 'toor'
const host = '127.0.0.1'
const port = '43782'
const rpcHref = `${protocol}://${rpcuser}:${rpcpassword}#${host}:${port}/`
const bitcoinRpc = createBitcoinRpc(rpcHref)
export default ({ app }, inject) => {
inject('bitcoinRpc', (method) =>
bitcoinRpc(method).then((result) => console.log('That was easy!', result))
)
}
This is my nuxt.config.js file:
...
plugins: [{ src: '#/plugins/gun.js' }, { src: '#/plugins/rpc.js' }],
...
If I call this.$bitcoinRpc('getnewaddress') somewhere in the component methods, then I get an error, but if I call this method inside the rpc plugin itself, then everything works as expected:
// plugins/rpc.js:
// Declare constants and inject above
...
bitcoinRpc('getnewaddress').then((result) =>
console.log('That was easy!', result)
)
I get the expected result in the terminal:
That was easy! 2N8LyZKaZn5womvLKZG2b5wGfXw8URSMptq 14:11:21
Explain what I'm doing wrong?
The method outlined by me was correct.
The error that occurred was caused by the fact that on the client side it was not possible to use the server side libraries.

Node.js: Awaiting a Require

I'm new the Node.js and I've been working with a sample project by a third party provider and I'm trying to use Azure Key Vault to store configuration values.
I'm having trouble getting a process to wait before executing the rest. I'll try to detail as much as I know.
The sample project has a file named agent.js which is the start page/file. On line 16 (agent_config = require('./config/config.js')[process.env.LP_ACCOUNT][process.env.LP_USER]) it calls a config file with values. I'm trying to set these value using Key Vault. I've tried many combinations of calling functions, and even implementing async / await but the value for agent_config always contains a [Promise] object and not the data returned by Key Vault.
If I'm right, this is because the Key Vault itself uses async / await too and the config file returns before the Key Vault values are returned.
How can Key Vault be added/implemented in a situation like this?
Here's what I've tried:
First updated agent.js to
let agent_config = {};
try {
agent_config = require('./config/config.js')['123']['accountName'];
} catch (ex) {
log.warn(`[agent.js] Error loading config: ${ex}`)
}
console.log(agent_config);
Test 1
./config/config.js
const KeyVault = require('azure-keyvault');
const msRestAzure = require('ms-rest-azure');
const KEY_VAULT_URI = 'https://' + '{my vault}' + '.vault.azure.net/' || process.env['KEY_VAULT_URI'];
function getValue(secretName, secretVersion) {
msRestAzure.loginWithAppServiceMSI({ resource: 'https://vault.azure.net' }).then((credentials) => {
const client = new KeyVault.KeyVaultClient(credentials);
client.getSecret(KEY_VAULT_URI, secretName, secretVersion).then(
function (response) {
return response.Value;
});
});
}
module.exports = {
'123': {
'accountName': {
accountId: getValue('mySecretName', '')
}
}
};
Results
{ accountsId: undefined }
Test 2
Made getValue an async function and wrapped it around another function (tried without the wrapping and didn't work either)
./config/config.js
const KeyVault = require('azure-keyvault');
const msRestAzure = require('ms-rest-azure');
const KEY_VAULT_URI = 'https://' + '{my vault}' + '.vault.azure.net/' || process.env['KEY_VAULT_URI'];
async function getValue(secretName, secretVersion) {
msRestAzure.loginWithAppServiceMSI({ resource: 'https://vault.azure.net' }).then((credentials) => {
const client = new KeyVault.KeyVaultClient(credentials);
client.getSecret(KEY_VAULT_URI, secretName, secretVersion).then(
function (response) {
return response.Value;
});
});
}
async function config() {
module.exports = {
'123': {
'accountName': {
accountId: await getValue('mySecretName', '')
}
}
};
}
config();
Results
{}
Test 3
Made getValue an async function and wrapped it around another function (tried without the wrapping and didn't work either)
./config/config.js
const KeyVault = require('azure-keyvault');
const msRestAzure = require('ms-rest-azure');
const KEY_VAULT_URI = 'https://' + '{my vault}' + '.vault.azure.net/' || process.env['KEY_VAULT_URI'];
async function getValue(secretName, secretVersion) {
return msRestAzure.loginWithAppServiceMSI({ resource: 'https://vault.azure.net' })
.then((credentials) => {
const client = new KeyVault.KeyVaultClient(credentials);
return client.getSecret(KEY_VAULT_URI, secretName, secretVersion).then(
function (response) {
return response.Value;
});
});
}
module.exports = {
'123': {
'accountName': {
accountId: getValue('mySecretName', '')
}
}
};
config();
Results
{ accountId: { <pending> } }
Other
I've tried many others ways like module.exports = async (value) =< {...} (found through other questions/solutions without success.
I'm starting to think I need to do some "waiting" on agent.js but I haven't found good info on this.
Any help would be great!
One issue is that your getValue function is not returning anything as your returns need to be explicit.
(and without the promise being returned, there's nothing to await on)
async function getValue(secretName, secretVersion) {
return msRestAzure.loginWithAppServiceMSI({ resource: 'https://vault.azure.net' })
.then((credentials) => {
const client = new KeyVault.KeyVaultClient(credentials);
return client.getSecret(KEY_VAULT_URI, secretName, secretVersion).then(
function (response) {
return response.Value;
});
});
}
You could also get away with less explicit returns using arrow functions..
const getValue = async (secretName, secretVersion) =>
msRestAzure.loginWithAppServiceMSI({ resource: 'https://vault.azure.net' })
.then(credentials => {
const client = new KeyVault.KeyVaultClient(credentials);
return client.getSecret(KEY_VAULT_URI, secretName, secretVersion)
.then(response => response.Value);
});
Introducing the Azure Key Vault read, which is async, means your whole config read is async. There' nothing you can do to get around that. This will mean that the code that uses the config will need to handle it appropriately. You start by exporting an async function that will return the config..
async function getConfig() {
return {
'123': {
'accountName': {
accountId: await getValue('mySecretName', '')
}
}
};
}
module.exports = getConfig;
In your agent code you call that function. This will mean that your agent code will need to be wrapped in a function too, so maybe something like this..
const Bot = require('./bot/bot.js');
const getConfig = require('./config/config.js');
getConfig().then(agentConfig => {
const agent = new Bot(agentConfig);
agent.on(Bot.const.CONNECTED, data => {
log.info(`[agent.js] CONNECTED ${JSON.stringify(data)}`);
});
});
The package azure-keyvault has been deprecated in favor of the new packages to deal with Keyvault keys, secrets and certificates separately. For your scenario, you can use the new #azure/keyvault-secrets package to talk to Key Vault and the new #azure/identity package to create the credential.
const { SecretClient } = require("#azure/keyvault-secrets");
const { DefaultAzureCredential } = require("#azure/identity");
async function getValue(secretName, secretVersion) {
const credential = new DefaultAzureCredential();
const client = new SecretClient(KEY_VAULT_URI, credential);
const secret = await client.getSecret(secretName);
return secret.value;
}
The DefaultAzureCredential assumes that you have set the below env variables
AZURE_TENANT_ID: The tenant ID in Azure Active Directory
AZURE_CLIENT_ID: The application (client) ID registered in the AAD tenant
AZURE_CLIENT_SECRET: The client secret for the registered application
To try other credentials, see the readme for #azure/identity
If you are moving from the older azure-keyvault package, checkout the migration guide to understand the major changes

Angular 6 - Initialize service with async calls before dependency injection

I have a service which based on the environment file, loads up a configuration object in memory. However, the reading of the settings is asynchronous and the application starts before even all the settings are loaded and crashes. Is there any way to 'await' for these functions before the dependency injection is complete.
My service :
import { Injectable } from '#angular/core';
import { IAppConfig } from '../models/app-config.model';
#Injectable()
export class AppConfig {
settings: IAppConfig;
version: any;
constructor() {
this.loadConfig();
}
// reads env.json file
// based on which environment it is loads config setting from
// environment specific config settings.
public loadConfig() {
return new Promise((resolve, reject) => {
const envFile = '/env.json';
this.readJsonFile(envFile).
then((envData) => {
const configFile = `assets/appconfigs/config.${envData.env}.json`;
this.version = envData.version;
this.readJsonFile(configFile).
then((configsettings) => {
this.settings = configsettings;
resolve(this.settings);
});
});
});
}
// reads json file and returns the json object promise
public readJsonFile(jsonUrl: string): any {
return new Promise((resolve, reject) => {
let retObject: any;
const xhr = new XMLHttpRequest();
xhr.overrideMimeType('application/json');
xhr.open('GET', jsonUrl, true);
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
retObject = JSON.parse(xhr.responseText);
resolve(retObject);
} else {
reject(`Could not load file '${jsonUrl}': ${xhr.status}`);
}
}
};
xhr.send(null);
});
}
}
I want the settings object to be loaded before the application fires up. The other way was to make this class static and call loadConfig but it is a nightmare for testbeds. Is there anything I could specify when providing the service in the module?
I'm not sure about this solution (untested code), but I guess you could async/await everything.
Is there any reason why you're not using Angular's http service, and instead, making a manual XMLHttpRequest?
It could be something like this :
constructor() {
this.run()
}
async run() {
await this.loadConfig()
}
// reads env.json file
// based on which environment it is loads config setting from
// environment specific config settings.
public loadConfig() {
return new Promise( async (resolve, reject) => {
const envFile = '/env.json';
let envData = await this.readJsonFile(envFile)
const configFile = `assets/appconfigs/config.${envData.env}.json`;
this.version = envData.version;
this.settings = await this.readJsonFile(configFile);
resolve(); // No need to pass this.settings to resolve()
});
}
// reads json file and returns the json object promise
public readJsonFile(jsonUrl: string): any {
return this.http
.get(jsonUrl)
.map(res => res.json())
.toPromise()
}
I want the settings object to be loaded before the application fires up.
You could/should make use of the injection token APP_INITIALIZER: angular.io/APP_INITIALIZER
In your case, something like below code would suffice.
In your AppModule:
{
provide: APP_INITIALIZER,
useFactory: initApp,
deps: [AppConfig]
}
export function initApp(appConfig: AppConfig) {
return () => appConfig.loadConfig();
}
More information on factory providers: angular.io/FactoryProvider

Categories

Resources