Code in Python 3. Bundle to Javascript.
Adds a Py3 -> Javascript
transpiler to your Wepack build tool chain.
Web scripts are no longer simple Javascript files linked with <script src=...>
. Using bundlers like Webpack and Parcel, today's web projects sport languages like TypeScript, CoffeeScript, SCSS, Less, and ES6 Javascript--often mixed together in a single project.
Bundling is the art of collecting all this diversity into bundles that browsers can understand and run efficiently. And while the bundler runs the overall process, it needs plugins (like this one) to transpile everything into plain 'ole Javascript, CSS, and HTML.
This package is exactly that glue: it runs Transcrypt (the .py -> .js
transpiler) whenever Wepack finds a .py
file, effectively turning Python into Javascript automagically.
Why?
- Why Python? If you haven't cried "abomination!" yet, it's probably safe to assume that you and I share a love for a certain Monty-Python-inspired language. Perhaps you even share my dislike for that other language we're forced to use. :)
- Why Transcrypt? It's is the best implementation I've seen to date to get Python into the browser. Of the many approaches taken over the years, transpiling to Javascript seems to be the "right" way. Plus, you get to say cool words like "transpiling". (I'm not the author of transcrypt, if you are wondering.)
- Why Bundle? Bundling represents today's best practices approach for Javascript development. Bundlers are the backbone of the toolchain, and they create efficient, minified websites that run on any browser.
- Why Webpack? It's the standard right now.
- Why transcrypt-loader? To glue it all together. Just don't inhale too much.
Installation
- Install Transcrypt. Instructions are at the website. I'd suggest trying out a few examples so you get used to transcrypt before moving on to bundling. Ensure you can create python, compile it, and run within a browser.
- Install npm and Wepack. Instructions are at the websites.
- Create an empty folder, and initialize npm:
npm init
. (all commands are done in a console within your project directory) - Install Webpack:
npm install webpack webpack-cli --save-dev
- Ensure Webpack runs within your project folder:
npx webpack --help
- Install this plugin:
npm install transcrypt-loader --save-dev
- Ensure Transcrypt runs within your project folder:
python3 -m transcrypt
Tutorial
If you haven't done it yet, install everything (above).
Open a console and change to your project directory (the one with package.json
in it). All of the following commands and files are done in that root project folder.
Configuration
Ensure package.json
contains the following (your package versions will likely be different):
"devDependencies":
Create the Webpack configuration file in your project directory:
webpack.config.json
moduleexports = devtool: 'inline-source-map' entry: "./main.py" mode: 'development' module: rules: test: /\.py$/ loader: 'transcrypt-loader' options: {} ;
The options (empty in this example) allow you to change the transcrypt command and arguments.
Source Files
Create the following two files:
index.html
My First App
main.py
# get the "app" div and add an ordered listapp = document.getElementById("app")ol = app.appendChild(document.createElement('ol')) # add a few names to the listfor name in [ 'Homer', 'Marge', 'Bart', 'Lisa', 'Maggie' ]: li = ol.appendChild(document.createElement('li')) li.textContent = f'{name} Simpson'
Your linter might be complaining here because transcrypt python isn't quite regular python. In the above code, we can use
document
because ourpy -> js
runs in the browser (even if the python linter is upset by it). ```
Bundle 'n Serve
Run the bundler:
npx webpack --watch
Webpack bundles everything into the dist/
folder, and then it starts a server daemon. Webpack continues to watch your project for changes, so watch the log messages as you edit and save.
Open your browser, select File | Open, and open index.html
.
Boom, baby! Python-style lists
, f-strings
, and for
loops, right in the browser!
If things don't seem to work when you open the web page, be sure to check the browser console for messages.
You can see the JS generated by Transcrypt in the
__target__
folder. Webpack uses these files when bundling. These files can also be incredibly useful when transpiling doesn't happen as expected. The generated JS is pretty readable, and a fewconsole.log
statements right in the generated stuff is often the ticket.
Getting Fancy
Let's continue the example with a few callbacks:
index.html
My Second App Add Item Remove Item
main.py
def ready(): app = document.querySelector("#app") ol = app.appendChild(document.createElement('ol')) counter = 1 def addButtonClicked(): nonlocal counter li = ol.appendChild(document.createElement('li')) li.textContent = ', '.join([ 'Item {}'.format(i) for i in range(counter, counter+10) ]) counter += 10 document.querySelector('#addButton').addEventListener('click', addButtonClicked) def removeButtonClicked(): if ol.lastChild: ol.removeChild(ol.lastChild) document.querySelector('#removeButton').addEventListener('click', removeButtonClicked) document.addEventListener("DOMContentLoaded", ready)
- Event callbacks:
ready
runs when the page is ready, and the buttons have click events. If you're used to Javascript's nested functions, it can be off-putting to see how callbacks have to be done here. Python just doesn't support anonymous functions, and lambdas are too limited. You'll get used to it, and perhaps you'll even grow to like it. But I do wish Python supported ruby-style, inline functions. - nonlocal: Don't hate on me here. It's just an example to highlight differences between Py and JS. The
nonlocal
statement is not actually needed. While omitting it would break in regular Python, the JS closure it becomes makes it work with or withoutnonlocal
. Just like a good Thanksgiving meal, it all ends up together in the end (as JS in the browser, in this case). - List comprehension: Love those things. Waaay better than mapping with embedded functions. SETL ftw! (with comprehensions, anyway)
Imports
Let's add some imports! We'll do both a Python import and a pure JS import. You'll need four files - careful with the extensions!
index.html
My Third App Add Item Remove Item
main.py
from .counter import Counterfrom .remove import removeButtonClicked def ready(): app = document.querySelector("#app") ol = app.appendChild(document.createElement('ol')) counter = Counter() def addButtonClicked(): li = ol.appendChild(document.createElement('li')) li.textContent = counter.getNext() document.querySelector('#addButton').addEventListener('click', addButtonClicked) # removeButtonClicked function is imported at top document.querySelector('#removeButton').addEventListener('click', removeButtonClicked) document.addEventListener("DOMContentLoaded", ready)
counter.py
class Counter(object): def __init__(self): self.counter = 1 def getNext(self): items = [ 'Item {}'.format(i) for i in range(self.counter, self.counter + 10) ] self.counter += 10 return ', '.join(items)
remove.js
{ let ol = document ol}
- Mixing of Python and Javascript Files: The main python file is able to import other python files AND JS files! It works the other way too.
- Python Class: The counter variable has become a proper Python class. It's all grown up! Perhaps someday, it could even become a real iterator.
- ES6 Javascript: The JS file is using ES6 syntax. Webpack will make it backwards-compatible during bundling (thanks Babel!).
Deploy
When you're ready to deploy production code, change the mode in webpack.config.js
to "production". Then run:
npx webpack
Webpack will produce minified, efficient, production-ready bundles. Deploy everything in the dist/
folder.
Code in Python, Bundle to Javascript, Deploy, ??, $$$!