{"id":13492473,"url":"https://github.com/leporello-js/leporello-js","last_synced_at":"2025-03-28T10:32:14.809Z","repository":{"id":166453734,"uuid":"530099072","full_name":"leporello-js/leporello-js","owner":"leporello-js","description":null,"archived":false,"fork":false,"pushed_at":"2025-02-05T19:12:47.000Z","size":7180,"stargazers_count":159,"open_issues_count":1,"forks_count":2,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-02-05T20:35:48.470Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://leporello.tech/","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/leporello-js.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":["leporello-js"]}},"created_at":"2022-08-29T06:47:44.000Z","updated_at":"2025-02-05T19:12:51.000Z","dependencies_parsed_at":null,"dependency_job_id":"997ba91e-19d3-48bd-9f31-cd3f9c543737","html_url":"https://github.com/leporello-js/leporello-js","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leporello-js%2Fleporello-js","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leporello-js%2Fleporello-js/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leporello-js%2Fleporello-js/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leporello-js%2Fleporello-js/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/leporello-js","download_url":"https://codeload.github.com/leporello-js/leporello-js/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246012575,"owners_count":20709468,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-31T19:01:06.381Z","updated_at":"2025-03-28T10:32:09.801Z","avatar_url":"https://github.com/leporello-js.png","language":"JavaScript","funding_links":["https://github.com/sponsors/leporello-js"],"categories":["JavaScript"],"sub_categories":[],"readme":"# Leporello.js\n\nLeporello.js is an interactive JavaScript environment with a time-travel debugger\n\n[\u003cimg src=\"docs/images/video_cover.png\" width=\"600px\"\u003e](https://vimeo.com/845773267)\n\n## **[Website](https://leporello.tech)**\n## **[Try online](https://app.leporello.tech)**\n\n## Leporello.js is funded solely by your donations\n\nSupport us on [Github Sponsors](https://github.com/sponsors/leporello-js) and be the first to gain access to the Leporello.js Visual Studio Code plugin with TypeScript support.\n\n## Features\n\n### Going beyond the REPL\nYour code is executed instantly as you type, with the results displayed next to it. No need to set breakpoints for debugging. Just move the cursor to any line and see what's happening.\n\n### Next level debugging capabilities\nVisualise and navigate a dynamic call graph of your program in a time-travel manner.\n\n### Develop HTML5 apps interactively\nModify your code and instantly see the updated version without losing the application state. Interact with your app and debug it later, similar to using a time machine.\n\n### Save time when working on IO-heavy programs\nIO operations are traced and transparently replayed on subsequent program executions.\n\n### Self-hosted\nLeporello.js source code is developed within Leporello.js itself\n\n## Supported javascript subset\n\nVariables are declared using the `const` or 'let' declaration. The use of `var` is not supported.\n\nLoops of any kind are not supported. Instead, consider using recursion or array functions as alternatives.\n\nThe `if` / `else` statements can only contain blocks of code and not single statements (TODO).\n\nBoth traditional functions and arrow functions, with block bodies and concise bodies, are supported. Method definitions, however, are not currently supported.\n\nClasses are not supported at the moment. The `this` keyword is not currently supported. The `new` operator can be used for instantiating built-in classes or classes imported from [third-party](#importing-third-party-libs) libs.\n\n`switch` statements will be supported in future updates.\n\nSupport for `try`, `catch`, and `finally` blocks is planned for future updates. However, the `throw` statement is currently supported.\n\nES6 modules are fully supported. Note that circular module dependencies are not currently supported and may lead to IDE crashes (with a TODO for improvement). Import/export aliases are not supported, and exporting `let` variables is also not supported. Additionally, `import.meta` is not currently supported.\n\n`async` functions and `await` are fully supported.\n\nGenerators are not supported.\n\nDestructuring is supported.\n\nSome operators are not supported at the moment, including:\n  - Unary plus\n  - Bitwise operators\n  - `in`\n  - `void`\n  - Comma operator\n  - Increment and decrement\n  - `delete`\n\n## Importing third-party libs\n\nTo enable its comprehensive functionalities, Leporello.js parses and instruments your source code. Should you wish to import a module as a black box, preventing the ability to step into its functions, you can utilize the `external` pragma. For instance:\n\n```\n/* external */\nimport {Foo} from './path/to/foo.js';\n```\n\n`external` pragma is just a comment that contains only the literal string `external` (both styles for comments and extra whitespaces are allowed).\n\nIf a module path is a non-local path including a protocol and a host then it is always imported as an external module. Example:\n\n![External import](docs/images/external_import.png)\n\nNow the module is imported as a black box - you cannot debug `BigNumber` methods.\n\n## IO\n\nTo enhance the interactive experience, Leporello.js traces the calls made to IO functions within your application. This trace can be replayed later, enabling you to program iteratively by making incremental changes to your code and promptly receiving feedback.\n\nThe current list of built-in functions for which calls are traced includes:\n- `Date`\n- `Math.random()`\n- `fetch`\n- `Response` methods:\n    - `arrayBuffer`\n    - `blob`\n    - `formData`\n    - `json`\n    - `text`\n- `setTimeout`\n- `clearTimeout`\n\nLeporello.js follows this process to manage IO calls:\n- Initially, when the code is run, Leporello.js traces all IO calls, storing arguments and return values in an array as a trace.\n- Whenever you edit your code, Leporello.js attempts to execute it, using the results of IO calls from the trace (replay).\n- During replay, when an IO call is made, Leporello.js compares the current call to the traced call in the array. It checks if the function and arguments are the same. If they match, Leporello.js returns the result from the trace.\n- To compare arguments for equality, Leporello.js uses deep equality comparison with `JSON.stringify`.\n- If they do not match, the trace is discarded, and Leporello.js executes the code again, this time without the trace. This process populates a new trace array.\n\nAdditionally, there are options to manually discard the trace, including a button and a hotkey for this purpose.\n\n## Hotkeys\n\nSee built-in Help\n\n## Editing local files\n\nEditing local files is possible via [File System Access API](https://developer.mozilla.org/en-US/docs/Web/API/File_System_Access_API). Click \"Allow access to local project folder\" to grant access to local directory.\n\n## Selecting entrypoint module\n\nAfter you granted local filesystem access you can select which javascript file\nto run. See the following picture\n\n![Entrypoint module](docs/images/entrypoint.png)\n\n## Selecting html file\n\nBy default code in run in context of empty HTML file. If you want to use custom\nHTML files with third party scripts or CSS stylesheets, you should choose HTML\nfile:\n\n![HTML file](docs/images/html_file.png)\n\nIn typical HTML5 app you add to your html file a `script` element pointing to\nyour entry js module, like this:\n\n```html\n\u003cscript type='module' src='index.js'\u003e\u003c/script\u003e\n```\n\nBecause Leporello has built in bundler, you dont point to your entry module in\nHTML file. Instead, you [select entrypoint module in\nUI](#selecting-entrypoint-module).\n\nIf you want to use the same HTML file both for developing in Leporello.js and\nin production, you can do it like this:\n\n```html\n\u003cscript type='module'\u003e\n  if(new URLSearchParams(window.location.search).get('leporello') == null) {\n    import('./src/index.js');\n  }\n\u003c/script\u003e\n```\n\nLeporello.js appends `?leporello` query parameter to your HTML file, so you can\ntest if HTML file is run in Leporello.js or in production.\n\nYou can add javascript libraries by including `script` tag to HTML file. If the library is exposing globals, they will be available in your javascript code after you select that HTML file as an entrypoint.\n\n\n## Run and debug UI code in separate window\n\nBy default your code is run in invisible iframe. If you want to run and debug\nUI code then you can open separate browser window. Click \"(Re)open app window\"\nin statusbar or press corresponding hotkey. New browser window will be opened\nand your code will be run in that window.\n\nWhile you interacting with your app in separate browser tab, all function calls\nare recorded. You can inspect and debug them.\n\nTo try live example, grant file system access to\n[./docs/examples/preact](./docs/examples/preact) folder. Then select `index.js`\nas an entrypoint and click \"(Re)open app window\". You will see the app where\nyou can calculate Fibonacci numbers:\n\n![Entrypoint module](docs/images/fib_ui.png)\n\nTry to click buttons and then get back to Leporello window. Now you can see\nthat all function calls have been recorded and you can inspect and debug\nthem:\n\n![Deferred calls](docs/images/deferred_calls.png)\n\n\u003c!--You can even run and debug Leporello.js in Leporello.js! To do this:\n\n- Check out Leporello.js repo and grant local filesystem access to root project directory\n- Select `src/launch.js` as an entrypoint\n- Select `index.html` as html file\n- Click \"(Re)open app window\"\n\nNew instance of Leporello.js will be opened in new browser tab.\n\nThe only problem is that both instances of Leporello.js will share the same\nlocalStorage. (TODO - inject localStorage implementation to opened window, that\nallows to share localStorage between host Leporello.js instance and window\nwhere code is run)\n--\u003e\n\n## Saving state between page reloads\n\nLeporello.js allows preserving the state of the application between page reloads. To achieve this, Leporello.js provides a special API:\n\n```javascript\nwindow.leporello.storage.get(key: string)\nwindow.leporello.storage.set(key: string, value: any)\n```\n\nUnlike localStorage and sessionStorage, these functions allow saving and retrieving non-serializable objects.\n\nThe storage can be cleared using the \"(Re)open app window\" button.\n\nYou can try the online demo [here](https://app.leporello.tech/?example=todos-preact). Create TODO items, then edit the code, and you will observe that your TODOs are preserved.\n\nThe code for interacting with the Leporello API is in the file `app.js`. When `app.js` module initializes, it checks whether Leporello.js API is present and loads app state:\n\n```javascript\nlet state\n\nif(globalThis.leporello) {\n  // Get initial state from Leporello storage\n  state = globalThis.leporello.storage.get('state')\n}\n```\n\nLater, when state changes, it saves it back to the storage:\n\n```javascript\n// on state change\nif(globalThis.leporello) {\n  // Save state to Leporello storage to load it after page reload\n  globalThis.leporello.storage.set('state', state)\n}\n```\n\n## Run Leporello locally\nTo run it locally, you need to clone repo to local folder and serve it via HTTPS protocol (HTTPS is required by [File System Access API](https://developer.mozilla.org/en-US/docs/Web/API/File_System_Access_API)). See [How to use HTTPS for local development](https://web.dev/how-to-use-local-https/)\n\n## Running test suite\nrun tests in node.js:\n\n```\nnode test/run.js\n```\n\nrun tests in leporello itself:\n\n![Tests](docs/images/test.png)\n\n- grant local folder access\n- select `test/run.js` as entrypoint\n\n\n## Roadmap\n\n* Use production level JS parser, probably TypeScript parser\n* Implement VSCode plugin\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fleporello-js%2Fleporello-js","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fleporello-js%2Fleporello-js","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fleporello-js%2Fleporello-js/lists"}