This plugin allows developing custom visuals by using webpack to build a visual package.
Provides following functionality:
- Creates assets for developer server
- Creates *.pbiviz package.
Plugin config description
const defaultOptions = {
visual: {
name: "Visual name",
displayName: "Visual name for displaying in visuals panel",
guid: `Unique GUID for the visual (generated by plugin)`,
visualClassName: "Visual class name, it is used by visual plugin to create instance of the visual",
version: "Visual version",
description: "Visual description",
supportUrl: "URL for support"
},
author: "Author of the visual",
apiVersion: "API version"
capabilities: {
// Visual capabilities
},
iconImage: "Icon file as base64 string",
devMode: "development mode",
packageOutPath: "location to create *.pbiviz file",
generateResources: "it is used --resources flag in pbiviz tools",
generatePbiviz: "it is used by --no-pbiviz flag in pbiviz tools",
certificationAudit: "scan the bundle for forbidden calls (fetch, eval, XMLHttpRequest) and report them as errors",
certificationFix: "remove forbidden calls from the bundle by replacing them with undefined; takes precedence over certificationAudit when both are set"
};-
Install all required libraries to build a visual
npm i webpack webpack-cli powerbi-visuals-webpack-plugin mini-css-extract-plugin webpack-bundle-analyzer extra-watch-webpack-plugin ts-loader json-loader less-loader less css-loader webpack-dev-server --save-dev
-
Install optional libraries to build JSX files
If you aren't going to build JSX files, then you can skip this step and delete all babel in section 5.
npm i @babel/preset-react @babel/runtime @babel/runtime-corejs3 @babel/core @babel/preset-env babel-loader --save-dev
-
Install the latest version of API package
npm i powerbi-visuals-api --save
-
Create SSL certificates (optional)
You need generate SSL certificates manually or copy files from powerbi-visuals-tools instance.
Read more about certificates in documentation.
Also, you can use certificates from
powerbi-visuals-toolsor use autogenerated certificates bywebpack-dev-server. Just skip section 4 to use webpack-dev-server certificates.4.1 Run the following command:
npm i powerbi-visuals-tools@latest --save-devto install the latest version of tools
4.2 create script command to generate certificate.
Add into
scriptssection ofpackage.jsoncommand"cert": "pbiviz --install-cert":... "scripts": { "cert": "pbiviz --install-cert" }, ...
Execute command
npm run cert. You should get message:error Certificate not found. The new certificate will be generated info Certificate generated. Location is <visual root>\node_modules\powerbi-visuals-tools\certs\PowerBICustomVisualTest_public.pfx. Passphrase is '<YOUR_PASSPHRASE>'
Apply path
node_modules\powerbi-visuals-tools\certs\PowerBICustomVisualTest_public.pfxin webpack.config.js athttpssection ofdevServerparameters:... https: { pfx: fs.readFileSync(path.join(__dirname, `node_modules/powerbi-visuals-tools/certs/PowerBICustomVisualTest_public.pfx`)), // for windows passphrase: "<YOUR_PASSPHRASE>", requestCert: true }, ... -
Use sample of config webpack 5. (copy into
webpack.config.js)const path = require("path"); // webpack 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"); // 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 generated by the webpack-plugin const visualSourceLocation = "../../src/visual" // This path is used inside of the generated plugin, so it depends on pluginLocation const statsLocation = "../../webpack.statistics.html"; const babelOptions = { presets: [ [ require.resolve('@babel/preset-env'), { "targets": { "ie": "11" }, useBuiltIns: "entry", corejs: 3, modules: false } ], "@babel/preset-react" // required for jsx files ], sourceType: "unambiguous", // tell to babel that the project can contains different module types, not only es2015 modules cacheDirectory: path.join(".tmp", "babelCache") // path for cache files }; // webpack sets process.env.WEBPACK_SERVE when running `webpack serve` (dev server). // Use it to switch automatically between developer-visual (dev server) and packaging behavior. const isProduction = !process.env.WEBPACK_SERVE; module.exports = { entry: { "visual.js": pluginLocation, }, target: "web", devtool: "source-map", mode: isProduction ? "production" : "development", optimization: { minimize: isProduction, // enable minimization for create *.pbiviz file less than 2 Mb, can be disabled for dev mode }, performance: { maxEntrypointSize: 1024000, maxAssetSize: 1024000 }, module: { rules: [ { test: /\.tsx?$/, exclude: /node_modules/, include: /powerbi-visuals-|src|precompile(\\|\/)visualPlugin.ts/, use: [ { loader: require.resolve('babel-loader'), options: babelOptions }, { loader: "ts-loader", options: { transpileOnly: false, experimentalWatchApi: false } } ], }, { test: /(\.js)x|\.js$/, use: [ { loader: require.resolve('babel-loader'), options: babelOptions } ] }, { test: /\.json$/, loader: 'json-loader', type: "javascript/auto" }, { test: /(\.less)|(\.css)$/, use: [ { loader: MiniCssExtractPlugin.loader }, { loader: 'css-loader' }, { loader: 'less-loader', options: { lessOptions: { paths: [path.resolve(__dirname, 'node_modules')] } } } ] }, { test: /\.(woff|ttf|ico|woff2|jpg|jpeg|png|webp|gif|svg|eot)$/i, type: 'asset/inline' }, { test: /powerbiGlobalizeLocales\.js$/, loader: LocalizationLoader } ], }, externals: { "powerbi-visuals-api": "null" }, resolve: { extensions: [".tsx", ".ts", ".jsx", ".js", ".css"], }, output: { clean: isProduction, // do NOT clean while serving: pbiviz.json is written outside webpack's asset pipeline and would be deleted by clean path: path.join(__dirname, ".tmp", "drop"), publicPath: 'assets', filename: "[name]", library: `${pbivizFile.visual.guid}${isProduction ? "" : "_DEBUG"}`, // dev server registers the visual under `<guid>_DEBUG` libraryTarget: "var", }, devServer: { static: { directory: path.join(__dirname, ".tmp", "drop"), // path with assets generated by webpack plugin publicPath: '/assets/' }, devMiddleware: { writeToDisk: true // required: flush emitted assets (status, visual.js, visual.css, source maps) to `.tmp/drop` so they are served under `/assets/*` (e.g. `/assets/status`) }, compress: true, // enable gzip compression port: 8080, // dev server port hot: false, server: { // cert files for dev server type: "https", options: { // keep it commented to use webpack generated certificate // key: path.join(__dirname, "certs","PowerBICustomVisualTest_public.key"), // for darwin, linux // cert: path.join(__dirname, "certs", "PowerBICustomVisualTest_public.cer"), // for darwin, linux // pfx: fs.readFileSync(path.join(__dirname, "certs", "PowerBICustomVisualTest_public.pfx")), // for windows // passphrase: "5031595470751755" // requestCert: true, } }, headers: { "access-control-allow-origin": "*", "cache-control": "public, max-age=0", } }, plugins: [ new MiniCssExtractPlugin({ filename: "visual.css", chunkFilename: "[id].css" }), new BundleAnalyzerPlugin({ // adds the ability to analyze the size of the bundle reportFilename: statsLocation, openAnalyzer: false, analyzerMode: `static` }), new PowerBICustomVisualsWebpackPlugin({ // custom visuals plugin instance with options ...pbivizFile, capabilities, visualSourceLocation, pluginLocation, apiVersion: powerbiApi.version, capabilitiesSchema: powerbiApi.schemas.capabilities, dependenciesSchema: powerbiApi.schemas.dependencies, devMode: !isProduction, // adds the `_DEBUG` suffix to the status file, plugin name and guid required by the Power BI developer visual generatePbiviz: true, generateResources: isProduction, modules: true, packageOutPath: path.join(__dirname, "dist"), }), new ExtraWatchWebpackPlugin({ files: [pbivizPath, capabilitiesPath], }), new webpack.WatchIgnorePlugin({ // visual plugin regenerates with the visual source, but it does not require relaunching dev server paths: [ path.join(__dirname, pluginLocation), "./.tmp/**/*.*", "./.tmp/**/**/*.*" ] }), new webpack.ProvidePlugin({ define: "fakeDefine", }), ], };
-
Add new script to build a visual package
Add new command
"package": "webpack"intoscriptssection ofpackage.json:"scripts": { "cert": "pbiviz --install-cert", "package": "webpack" }
Run
npm run packagecommand to create visual package.
Install webpack-dev-server:
npm i webpack-dev-server --save-devAdd command "start": "webpack serve" into scripts section of package.json :
"scripts": {
"cert": "pbiviz --install-cert",
"package": "webpack",
"start": "webpack serve"
}Run command npm run start to start dev server.
When you develop a visual through this plugin directly (i.e. with your own webpack.config.js instead of the pbiviz CLI), the dev server must expose the following files under the /assets/ route so Power BI can load the developer visual:
statusvisual.jsvisual.css- the related source maps
The plugin emits the status asset on every compilation and writes pbiviz.json to the drop folder, but the JS/CSS bundles and source maps are produced by webpack itself. By default webpack-dev-server keeps compiled assets in memory, so only pbiviz.json ends up in .tmp/drop, and requests such as https://localhost:8080/assets/status return 404 Not Found.
To match the behavior of the pbiviz CLI, make sure your devServer configuration:
- Serves the drop folder under the
/assets/route (devServer.static.directory=.tmp/drop,publicPath=/assets/). - Writes emitted assets to disk with
devMiddleware: { writeToDisk: true }. - Uses
output.publicPath: 'assets'so emitted assets resolve under/assets/*. - Keeps
output.cleandisabled while serving.pbiviz.jsonis written by the plugin directly to disk (it is intentionally not a webpack asset), soclean: truewould delete it after every rebuild and/assets/pbiviz.jsonwould return404. - Runs the plugin with
devMode: truewhile serving, so thestatusfile, plugin name and visual GUID get the_DEBUGsuffix that the Power BI developer visual expects (e.g.statuscontains<guid>_DEBUGinstead of<guid>). Keep the webpackoutput.libraryin sync (<guid>_DEBUG). WhendevModeistrue, minification is also typically disabled, so novisual.js.LICENSE.txtsidecar is produced.
The sample webpack.config.js above derives all of this from a single isProduction = !process.env.WEBPACK_SERVE switch: running webpack serve behaves like pbiviz start (developer visual, _DEBUG suffix, no clean, unminified), while running webpack builds the package. After starting the dev server, https://localhost:8080/assets/status should return 200 and .tmp/drop should contain status, visual.js, visual.css, pbiviz.json, and the source maps.
This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit https://cla.microsoft.com.
When you submit a pull request, a CLA-bot will automatically determine whether you need to provide a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repos using our CLA.
This project has adopted the Microsoft Open Source Code of Conduct. For more information see the Code of Conduct FAQ or contact opencode@microsoft.com with any additional questions or comments.