I am trying to create maps with custom visuals for powerBi, and I am trying to add the feature of search Geolocation to the map with auto-complete. To do the auto-complete, I intend to use the API, specifically the Azure maps API. But I don't have a way to conceal the API key.
I tried to add environment variables to the visual, but if I use dotenv.config(), I get the error "ReferenceError: global is not defined". Otherwise the process.env.API_KEY return "undefined".
Is there is a way to use environment variables or otherwise use secret, in custom visuals in pbiviz?
I use the following code to bring all env variables through for webpack and my custom viz.
This is up the top of my webpack config
const envFile = dotenv.config({
path: `.env.${currentDotEnv}`,
defaults: '.env',
systemvars: true,
silent: true,
}).parsed
// Combine env variables from the .env file and process.env
const combinedEnv = {
...process.env,
...envFile,
}
// Create an object with all env vars prefixed with 'process.env'
const envKeys = Object.keys(combinedEnv).reduce((prev, next) => {
prev[`process.env.${next}`] = JSON.stringify(combinedEnv[next])
return prev
}, {})
From there I get access to all of them in my running code via process.env.XXXX
.
This lets me have .env files for dev and for production builds in GitHub Actions I can pass secrets in securely to the build process.
Full webpack.config.js
const os = require('os')
const path = require('path')
const fs = require('fs')
const dotenv = require('dotenv')
const DotenvWebpack = require('dotenv-webpack')
const optimize = process.env.OPTIMIZE === 'true'
const currentDotEnv = optimize ? 'prod' : 'dev' // this is for loading the correct .env file only
const isProduction = process.env.NODE_ENV === 'production'
const envFile = dotenv.config({
path: `.env.${currentDotEnv}`, // Load environment-specific .env file
defaults: '.env', // Load the base .env file as defaults
systemvars: true, // Load system environment variables
silent: true, // Suppress warnings if .env files are missing
}).parsed
// Combine env variables from the .env file and process.env
const combinedEnv = {
...process.env,
...envFile,
}
// Create an object with all env vars prefixed with 'process.env'
const envKeys = Object.keys(combinedEnv).reduce((prev, next) => {
prev[`process.env.${next}`] = JSON.stringify(combinedEnv[next])
console.log(`process.env.${next}`, JSON.stringify(combinedEnv[next]))
return prev
}, {})
console.log('currentDotEnv', currentDotEnv)
console.log('isProduction', isProduction)
console.log('optimize', optimize)
// werbpack plugin
const webpack = require('webpack')
const { PowerBICustomVisualsWebpackPlugin, LocalizationLoader } = require('powerbi-visuals-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer')
const ExtraWatchWebpackPlugin = require('extra-watch-webpack-plugin')
const { VueLoaderPlugin } = require('vue-loader')
const PnpWebpackPlugin = require('pnp-webpack-plugin')
// api configuration
const powerbiApi = require('powerbi-visuals-api')
// visual configuration json path
const pbivizPath = './pbiviz.json'
const pbivizFile = require(path.join(__dirname, pbivizPath))
// the visual capabilities content
const capabilitiesPath = './capabilities.json'
const capabilities = require(path.join(__dirname, capabilitiesPath))
const pluginLocation = './.tmp/precompile/visualPlugin.ts' // path to visual plugin file, the file generates by the plugin
const visualSourceLocation = '../../src/visual' // This path is used inside of the generated plugin, so it depends on pluginLocation
// Build out the viz config
const pbiPluginConfig = {
...pbivizFile,
compression: optimize ? 9 : 0,
capabilities,
visualSourceLocation,
pluginLocation,
apiVersion: powerbiApi.version,
capabilitiesSchema: powerbiApi.schemas.capabilities,
dependenciesSchema: powerbiApi.schemas.dependencies,
devMode: false,
generatePbiviz: true,
generateResources: optimize,
modules: true,
packageOutPath: path.join(__dirname, 'dist'),
}
if (process.env.VERSION && pbiPluginConfig.visual != null) {
pbiPluginConfig.visual.version = process.env.VERSION
}
// string resources
const resourcesFolder = path.join('.', 'stringResources')
const localizationFolders = fs.existsSync(resourcesFolder) && fs.readdirSync(resourcesFolder)
// babel options to support IE11
const babelOptions = {
presets: [
[
require.resolve('@babel/preset-env'),
{
useBuiltIns: 'entry',
corejs: 3,
modules: false,
},
],
],
plugins: [
[
require.resolve('babel-plugin-module-resolver'),
{
root: ['./'],
},
],
PnpWebpackPlugin.moduleLoader(module),
],
sourceType: 'unambiguous', // tell to babel that the project can contains different module types, not only es2015 modules
cacheDirectory: path.join('.tmp', 'babelCache'), // path for chace files
}
const devServerConfig = {
static: {
directory: path.join(__dirname, '.tmp', 'drop'), // path with assets generated by webpack plugin
publicPath: '/assets/',
watch: true,
},
compress: true,
port: 8080, // dev server port
hot: false,
server: {
type: 'https',
options: {
key: optimize ? undefined : fs.readFileSync(path.resolve(`${os.homedir}/.office-addin-dev-certs/localhost.key`)),
cert: optimize ? undefined : fs.readFileSync(path.resolve(`${os.homedir}/.office-addin-dev-certs/localhost.crt`)),
ca: optimize ? undefined : fs.readFileSync(path.resolve(`${os.homedir}/.office-addin-dev-certs/ca.crt`)),
},
},
devMiddleware: {
writeToDisk: true,
},
headers: {
'access-control-allow-origin': '*',
'cache-control': 'public, max-age=0',
},
}
module.exports = {
entry: {
visual: pluginLocation,
},
optimization: {
concatenateModules: optimize,
flagIncludedChunks: optimize,
mangleExports: optimize,
mergeDuplicateChunks: optimize,
minimize: optimize,
moduleIds: optimize ? 'size' : 'named',
realContentHash: optimize,
removeAvailableModules: optimize,
removeEmptyChunks: optimize,
},
target: 'web',
devtool: 'source-map',
mode: 'development',
module: {
rules: [
{
test: /\.vue$/,
use: [
{
loader: require.resolve('vue-loader'),
options: {
compilerOptions: {
isCustomElement: tag => tag.startsWith('fluent-') || tag.startsWith('office-') || tag.startsWith('ms-'),
},
},
},
],
},
{
parser: {
amd: false,
},
},
{
test: /(\.ts)x|\.ts$/,
use: [
{
loader: require.resolve('babel-loader'),
options: {
presets: [
// '@babel/react',
'@babel/env',
],
},
},
{
loader: require.resolve('ts-loader'),
options: {
transpileOnly: false,
experimentalWatchApi: false,
appendTsSuffixTo: [/\.vue$/],
},
},
],
exclude: [/node_modules/],
include: /powerbi-visuals-|src|precompile\\visualPlugin.ts/,
},
{
test: /(\.js)x|\.js$/,
use: [
{
loader: require.resolve('babel-loader'),
options: babelOptions,
},
],
exclude: [/node_modules/],
},
{
test: /\.json$/,
loader: require.resolve('json-loader'),
type: 'javascript/auto',
},
{
test: /\.(css)?$/,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader'],
},
{
test: /\.(woff|ttf|ico|woff2|jpg|jpeg|png|webp|svg)$/i,
use: [
{
loader: 'base64-inline-loader',
},
],
},
],
},
externals: { 'powerbi-visuals-api': 'null' },
resolve: {
extensions: ['.tsx', '.ts', '.jsx', '.js', '.css', '.json', '.vue'],
alias: {
'@': path.resolve(__dirname, 'src'),
},
},
output: {
publicPath: '/assets',
path: path.join(__dirname, '/.tmp', 'drop'),
library: +powerbiApi.version.replace(/\./g, '') >= 320 ? pbivizFile.visual.guid : undefined,
libraryTarget: +powerbiApi.version.replace(/\./g, '') >= 320 ? 'var' : undefined,
},
devServer: optimize ? {} : devServerConfig,
externals:
powerbiApi.version.replace(/\./g, '') >= 320
? {
'powerbi-visuals-api': 'null',
fakeDefine: 'false',
}
: {
'powerbi-visuals-api': 'null',
fakeDefine: 'false',
corePowerbiObject: "Function('return this.powerbi')()",
realWindow: "Function('return this')()",
},
plugins: [
new DotenvWebpack({
path: `.env.${currentDotEnv}`, // Load environment-specific .env file
defaults: '.env', // Load the base .env file as defaults
systemvars: true, // Load system environment variables
silent: true, // Suppress warnings if .env files are missing
}),
new webpack.DefinePlugin({
__VUE_OPTIONS_API__: JSON.stringify(true),
__VUE_PROD_DEVTOOLS__: JSON.stringify(false),
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: JSON.stringify(false),
}),
new VueLoaderPlugin(),
new MiniCssExtractPlugin({
filename: '[name].css',
}),
// visual plugin regenerates with the visual source, but it does not require relaunching dev server
new webpack.WatchIgnorePlugin({
paths: [path.join(__dirname, pluginLocation), './.tmp/**/*.*'],
}),
// custom visuals plugin instance with options
new PowerBICustomVisualsWebpackPlugin(pbiPluginConfig),
new ExtraWatchWebpackPlugin({
files: [pbivizPath, capabilitiesPath],
}),
powerbiApi.version.replace(/\./g, '') >= 320
? new webpack.ProvidePlugin({
define: 'fakeDefine',
})
: new webpack.ProvidePlugin({
window: 'realWindow',
define: 'fakeDefine',
powerbi: 'corePowerbiObject',
}),
],
}
enter code here