Hi! Today I will show how to use python language inside your PCF, showing how the platform is so wide and you can do incredible things.
I'm a PCF lover 🥰, and when I love something in the technologic world, I try to understand each byte of the subject. In PCF I'm doing a lot of studies to better understand how works. And one thing that gave me curiously is that PCF use webpack to build the component. I thought, a lot of web components in the world use webpack, can we use too on PCF?
So I'm started to search what I can you on my PCF's? So I found a lot of cool things to use likes Sass(in the next post) and Python. And have a lot of others. Please comment what you like to use on the pcf that has not to support yet.
Let´s talk less and do more!
First things first, we know that run python direct on browser is not possible, but a few years a lot of developers are doing much effort to be possible, and with those people emerged many interesting project that makes this possible like Transcrypt, Brython, Jiphy and others. Those projects do the conversion from python to javascript.
So for this post I have used the Transcrypt project to work. You can check on the site for more information about it.
Let´s create a PCF project to start the process. After this let's install an npm project that helps us to use the transcrypt
Npm install --save-dev py-loader
Now we need to have the python installed on your machine and run the pip command to install the Transcrypt. On your command window type the following:
Pip install transcrypt
Good! Now we open the project on visual studio code and we need some changes on py-loader package because he works fine on JS projects but on PCF projects we need to change somethings.
The first thing that we need to do is go to the folder /node_modules/pcf-script and edit webpackConfig.js. This is the file that webpack uses to build the component, so we need to able to compile python files using the py-loader. Find the method getWebpackConfig() and find where the modules property.
Inside the rules we will add a rule to process python files, so add the current code:
{
test: /\.py$/,
loader: 'py-loader'
}
After, save the file.
Let´s edit the py-loader module to works properly with PCF. So go to folder /node_module/py-loader and open index.js file. And replace with current code below:
const cmd = require('node-cmd')
const fs = require('fs');
const { sep: slash } = require('path');
const path = require('path');
const loaderUtils = require('loader-utils');
const spawn = require('child_process').spawn;
const properName = name => name.replace(/^./, c => c.toUpperCase());
const listify = array => array.join(', ')
// make a comma-separated list ending with a '&' separator
.replace(/(, )[^,]*$/, s => ' & ' + s.split(', ')[1]);
module.exports = function (source) {
const compilers = {
transcrypt: {
switches: '-b -n -m',
folder: `${slash}__target__`,
install: 'pip install transcrypt',
python_version: '3.x',
sourcemaps: true
},
jiphy: {
switches: '',
folder: `.${slash}`,
install: 'pip install jiphy',
python_version: '2.x',
sourcemaps: false
},
pj: {
switches: '--inline-map --source-name %f -s -',
folder: `.${slash}`,
install: 'pip install javascripthon',
python_version: '3.x',
streaming: true,
sourcemaps: true
}
};
const options = loaderUtils.getOptions(this);
const compilerName = options && options.compiler || 'transcrypt';
const compiler = compilers[compilerName];
if (!compiler) {
throw new Error(`py-loader only supports ${
listify(Object.keys(compilers).map(properName))
} compilers at present. See README.md for information on using it with others.`);
}
compiler.name = compilerName;
const entry = this._module.resource;
//console.log(`py-loader: compiling ${entry} with ${compilerName}...`);
const basename = path.basename(entry, ".py");
const srcDir = path.dirname(entry, ".py");
const callback = this.async();
if (compiler.streaming) {
compiler.switches = compiler.switches.replace('%f', basename);
var child = spawn(compiler.name, compiler.switches.split(' '));
child.stdin.write(source);
var data = '';
var error = '';
child.stdout.on('data', function (js) {
data = data + js;
});
child.stderr.on('data', function (msg) {
error = error + msg;
});
child.on('exit', function () {
if (compiler.sourcemaps) {
sourcemapLine = data.split('\n').splice(-3,1)[0]; // Javascripthon specific?
sourceMap = new Buffer(sourcemapLine.substring(sourcemapLine.indexOf('base64,') + 7), 'base64').toString();
callback(error, data, sourceMap); }
else {
callback(error, data);
}
});
child.on('error', function(err) {
console.error(`Some error occurred on ${properName(compiler.name)} compiler execution. Have you installed ${properName(compiler.name)}? If not, please run \`${compiler.install}\` (requires Python ${compiler.python_version})`);
callback(err);
});
child.stdin.end();
}
else {
cmd.get(`${compiler.name} ${compiler.switches} ${srcDir}${slash}${basename}.py`, function(err, data, stderr) {
if (!entry.toLowerCase().endsWith(".py")) {
console.warn("This loader only handles .py files. This could be a problem with your webpack.config.js file. Please add a rule for .py files in your modules entry.");
callback(null, source);
}
if (!err) {
const filename = `${srcDir}${slash}${compiler.folder}${slash}${basename}.js`;
js = fs.readFileSync(filename, "utf8");
js = js.replace("./org.transcrypt.__runtime__.js", `./${compiler.folder}/org.transcrypt.__runtime__.js`);
fs.writeFileSync(filename,js,"utf8");
fs.unlinkSync(filename);
if (compiler.sourcemaps) {
const sourceMapFile = `${srcDir}${slash}${compiler.folder}${slash}${basename}.map`;
console.log(sourceMapFile);
sourceMap = fs.readFileSync(sourceMapFile, "utf8")
callback(null, js, sourceMap); }
else {
callback(null, js);
}
}
else {
console.error(`Some error occurred on ${properName(compiler.name)} compiler execution. Have you installed ${properName(compiler.name)}? If not, please run \`${compiler.install}\` (requires Python ${compiler.python_version})`);
callback(err);
}
});
}
}
OK! After this we are able to you python on you PCF. So let´s create a new python file inside the project. And put the current code on it.
class Hello:
def hello_world():
return "Hello from python"
Save the file with name "hello.py".
OK! After this, we are able to use python on your PCF. So let´s create a new python file inside the project. And put the code below on it.
// -ignore
import {Hello} from "./hello.py"
To use the python we need to pay attention to how we do the import. So as you see, we need to use the tag ts-ignore in the import, because python files are not supported OOB, thus we will raise an error when building. This tag will ignore errors in this part. Another thing is that we must use, is the alias using curly brackets({}). Because, if we don't use it when webpack builds, the javascript code will have a property name called "default" and you cannot have access to python objects and properties.
Now we can put Python object in the PCF. Remember, it is not OOB feature, so you will not have IntelliSense and auto-complete. In my example I call the Python code on the init() method to show the result:
public init(context: ComponentFramework.Context<IInputs>, notifyOutputChanged: () => void, state: ComponentFramework.Dictionary, container:HTMLDivElement)
{
// Add control initialization code
container.innerHTML = Hello.hello_world();
}
Now we can build and run the component. And there is the result!
Is that cool, no? \o/
If we can do this, we can have a big world that Power Platform gives to us.
I hope that you liked this post.😊