an old package on prod is not an issue if it is isolated and you have a plan for change, so if you have to transpile, do it and then work from the bottom.
There are some jobs that contain rather simple JavaScript snippets, and I was trying to design a first prototype that simply takes the JS parts and runs them in a transpiler.
In this respect, I found a couple of packages that could be leveraged: - js2py: https://github.com/PiotrDabkowski/Js2Py - mini-racer: https://github.com/bpcreech/PyMiniRacer Yet, both seem to be abandoned packages that might not be suitable for usage in production.
Therefore, I was thinking about parsing and translating Javascript's abstract syntax trees to Python. Whereas a colleague suggested I bring up an LLM pipeline.
How much of an overkill that might be? Has anyone else ever dealt with a JavaScript-to-Python migration and could share heads-ups on strategies or pitfalls to avoid?
an old package on prod is not an issue if it is isolated and you have a plan for change, so if you have to transpile, do it and then work from the bottom.
It sounds like a complete waste of time. If you are talking about small code snippets then simply write new original Python to replace them.
- If the scripts are primarily by the same author, it's likely they copy-pasted a lot of their functions throughout their scripts. Do something like a "Find All" in the repository holding the scripts for the function name. Regex will help a lot in this, and help you see if a function ever has variations on number of arguments, slight name changes or misspells, etc.
- Refine an AI prompt as you convert scripts over and over.
- AI (ChatGPT, Copilot, etc.) is going to be the best automation you can get for this, because often times conversions don't match up one-to-one in a language. Especially if your scripts use npm, you might not find one-to-one matches in PyPI. AI refactors will also force you to examine the conversions.
- Understand what tech debt could be introduced in a one-to-one conversion. There may be some language workarounds that make a lot more sense to introduce over exact conversions of workaround functions for the language. I've had to work with several legacy Python scripts that called functions that can be better written in your target language. For example, one of our functions was conditionally_get_keys(level1, level2, level3) to kind of recursively get a nested value. This didn't need to be a function when I rewrote it in JS, rather I just wrote the variable as something like `city = user.location?.city`. No need to one-to-one convert a function that can be better written with a JS language feature. You'll probably encounter this when you can more succinctly write list manipulations with slicing (e.g. `steppedList = fooList[::2]` instead of converting a function necessary in JS to do the same thing).
Sorry if you have a time-crunch with converting things, but I would recommend a more hands-on conversion strategy, leveraging AI to do the basic conversion and then manually testing, debugging the solutions.
Keys to success in a larger scale translation:
- don't redesign anything, do a port (see also, Typescript compiler to Go port)
- leverage LLMs interactively: per chunk (e.g. function), copy the old code into a comment in the new code, then use LLM completion to quickly fill out the translation
- get something basic up and running ASAP that you can test, ideally data-driven (inputs, expectations) tuples, that you can write scaffolds for execution of the old and the new code
- for every method / control flow ported, add tests that target the newly added code, validating it does the same as the old code
Some of this may be less applicable to scripts or harder to apply to imperative code, for which you might want to spend time converting side-effecting actions into data that can be asserted on (e.g. instead of performing commands, emit a list of commands); do this refactoring on the old code before porting.
Don't get tempted into doing refactorings as you go. When you notice an opportunity to refactor, create a bug for it. What you don't want to do is build up a list of transformations that increases the more code you port, and makes finishing everything harder and harder.
## Core Questions for Migration Scope Clarification
1. *What exactly needs to be preserved?* - Business outcomes only, or exact implementation details? - Current scheduling patterns or can they be optimized?
2. *What's the true scale?* - Number of workflows needing migration - Complexity spectrum of the JavaScript snippets - Frequency and criticality of each workflow
3. *What are the real constraints?* - Timeline requirements - Available expertise (JavaScript, Python, Airflow) - Downtime tolerance during transition
4. *What's the maintenance plan?* - Who will support the migrated workflows? - What documentation needs to be created? - How will knowledge transfer occur?
5. *What's the verification strategy?* - How will you validate correct migration? - What tests currently exist or need to be created? - What defines "successful" migration?
6. *What's unique to your environment?* - Custom integrations with other systems - Special CA Workflow features being utilized - Environmental dependencies
7. *What's the true purpose of this migration?* - Cost reduction, technical debt elimination, feature enhancement? - Part of larger modernization or standalone project? - Strategic importance versus tactical necessity
8. *What approaches have been eliminated and why?* - Complete Python rewrite - Containerized JavaScript execution - Hybrid approaches
9. *What would happen if this migration didn't occur?* - Business impact - Technical debt consequences - Opportunity costs
10. *Who are the true stakeholders?* - Who relies on these workflows? - Who can approve changes to functionality? - Who will determine "success"?
Answering these questions before diving into implementation details will save significant time and reduce the risk of misaligned expectations.
If they force an LLM on you, say that you are thrilled to use an LLM, give it a try and arrive at the conclusion that unfortunately LLMs are not up to the task. Bill the hours needed to arrive at that conclusion.
There's no need for software engineers to automate themselves away. Look at lawyers, they know how to bill and protect themselves.
Pitfalls to watch our for? Tons of them. Comparison is very different, modulus is different, .sort is different, object destructuring doesn't map nicely to python, lambda's won't map nicely to python, promises won't map to python. Labelled loops won't map nicely to python.
If your JS snippets are truly simple, just LLM translate and manually check. They're pretty good at the simple stuff.
The hard work will be validating that the code they write for you is exactly right. You would have to do that if you wrote the code yourself, too. The LLMs will accelerate the writing-the-code part but the manual QA work will still be on you: https://simonwillison.net/2025/Mar/11/using-llms-for-code/#y...
> Don't get tempted into doing refactorings as you go.
I would say those are the most important. We did so many migrations in the past 30 years and the only ones that went ok were the ones that held to these rules. If you don't, you are rapidly stuck in a lot of pain and probably you won't be able to get out.
If you have an ecosystem to keep compatibility with, I would look at compiling the JavaScript to WASM and running the WASM from Python, or some kind of sandboxing to continue running the JavaScript as-is.
This way you have an accurate idea of how your code is working before and after the port.
At my last job we used PythonMonkey to port our complex distributed computing JS Library to Python enabling us to reuse all the code and keep almost all the performance.
1. https://pythonmonkey.io/ and https://github.com/Distributive-Network/PythonMonkey 2. https://distributive.network/jobs/python-monkey
Separate the concerns: migrate the task orchestration to Airflow (or whatever) while keeping the actual Javascript task code largely unchanged.
The thing that really matters is how are you going to ship this?
You should figure out if there is a way it can be delivered incrementally.
Make sure it's easy yo roll back from new to old on as small a chunk as possible.
Make sure rollbacks and deploys don't require manual futzing.
Make sure it's easy for outside people to KNOW the status of things without asking you.
Make sure you have a way to coordinate with feature devs on when it's OK to work on a specific chunk.
Make sure you can test if things are working after you deploy a change.
After that you'll probably come up with like 30 ways to translate the code and use all of them until you find one that's actually tollerable.
Programmers have an unhealthy aversion to repetitive tasks. Sometimes you just have to do work-work. Happens all the time in other industries,
clock in at 9 do the same thing for 2 hours, take a break, do the same thing for 2 hours, lunch, 2 hours break, 2 hours, go home.
Repeat this for weeks if necessary, you can plan it out and predict when it will be done, if need be ask for more resources.
I've done similar inter-language changes, and I have always found it easier to not change the language of the business logic. (Unless the change gives you something really big - for me I often port stuff to numpy because I need the vectorized code, but that's only for very specific problems).
If I had this task, the place where my brain would go would be to find a way to compile JS into C and then use C calling conventions to call the functions from Python. Keep the JS code around so that if you need to change anything, you change it in JS and then recompile to C.
I don't know the JS space very well, but can you get a JS interpreter that lives in Python? That way you can call JS functions from Python?
I don't like transpiling, there's always enough differences between the languages that something bad happens. When I've run into issues like this, since I'm an "old guy", I tend to try to get everything into C calling conventions and use that as my base interface.
Worst case, there has to be some good JS interpreter that can give you a C interface that you could call from Python. So you'd have Python -> C -> JS and your business logic can still live in JS (if your port is because of efficiency and you need compiled code, then you can ignore me.)
The LLM's are just fine at AST translation, though they might inject their quirky preferences if you don't watch them carefully. Suggestion: use the LLM's, but interactively, to translate one script at a time, starting with the simpler ones. Tell the LLM to explicitly call out potential issues. Manually review each, and incorporate the issues and preferences you learn into the prompt. If all goes well, the process will quickly converge on a cut and paste job, but don't be tempted to fully automate it if it's a hundred scripts -- different matter if it's thousands.
Nice, and as a bonus you end up with a well tested system. Can't speak highly enough of data driven testing for this kind of system. Gives you such confidence.
> When you notice an opportunity to refactor, create a bug for it.
Did you have success in getting time to revisit all these bugs? Did you get pressure to fix them along the way (in either codebase)?
There was pressure from a reviewer in one area where the code could obviously be improved and I pushed back fairly hard in principle, but this was our first big project together and we were building trust, so I compromised in some leaf functions that presented the same API.