Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
44a6892
change apis to accept an option object directly
ceifa Jan 18, 2025
fce233a
convert bin to esm
ceifa Jan 18, 2025
867ae70
wip: new improved cli
ceifa Jan 18, 2025
eae545b
implement option to use raw node fs
ceifa Feb 18, 2025
2e0ab5f
drop support for node 18 and 20
ceifa Feb 18, 2025
580e25a
unify build command
ceifa Feb 18, 2025
32fdd5c
specify cjs
ceifa Feb 25, 2025
9fdcef4
add node fs support for windows
ceifa Feb 25, 2025
dbda8f0
more close to real api
ceifa Feb 25, 2025
d9a63f9
remove finalization registry check
ceifa Feb 25, 2025
1e04deb
stdin finally working (at least on linux)
ceifa Feb 25, 2025
048878a
add jsr publish
ceifa Feb 25, 2025
07096b3
update artifact package
ceifa Feb 25, 2025
2683947
lint
ceifa Feb 25, 2025
a315aba
fix jsr.json structure
ceifa Feb 25, 2025
68deb27
package name
ceifa Feb 25, 2025
84ee22c
concatenation
ceifa Feb 25, 2025
745fac6
force include
ceifa Feb 25, 2025
3b4d4e1
force exclude dist
ceifa Feb 25, 2025
f8f95f5
try not checking out repo
ceifa Feb 25, 2025
8acf55d
license
ceifa Feb 25, 2025
b3778d6
test older emscripten version
ceifa Feb 25, 2025
657a079
fix string
ceifa Feb 25, 2025
32af619
disable jsr publish
ceifa Feb 25, 2025
a34ed74
better api for stdio
ceifa Feb 26, 2025
e63fcb6
check for docker
ceifa Feb 26, 2025
ecc6388
changed library api to abstract wasm (but keep it exposed) and change…
ceifa Feb 26, 2025
9dac5f9
update packages, use oxc
ceifa Mar 13, 2026
9fd1b19
fix lua 5.5.1 implementation
ceifa Mar 13, 2026
d41aca8
add a test to ensure no regressions on 64bit numbers
ceifa Mar 14, 2026
a23e34d
optimize build size
ceifa Mar 14, 2026
c326891
lint and readme benchmark
ceifa Mar 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 91 additions & 14 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,101 @@ on:
branches: [main]

jobs:
publish:
build:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- name: Checkout repository
uses: actions/checkout@v6
with:
submodules: recursive
- uses: mymindstorm/setup-emsdk@v14
- name: Use Node.js 22.x
uses: actions/setup-node@v4

- name: Setup EMSDK
uses: mymindstorm/setup-emsdk@v14
with:
version: 5.0.2

- name: Use Node.js 24.x
uses: actions/setup-node@v6
with:
node-version: 22.x
- run: npm ci
- run: npm run lint:nofix
- run: npm run build:wasm
- run: npm run build
- run: npm test
- run: npm run luatests
- uses: JS-DevTools/npm-publish@v3
node-version: 24.x

- name: Install dependencies
run: npm ci

- name: Run lint (no fix)
run: npm run lint:nofix

- name: Build WASM
run: npm run build:wasm

- name: Build project
run: npm run build

- name: Run tests
run: npm test

- name: Run Lua tests
run: npm run luatests

- name: Upload build artifact
uses: actions/upload-artifact@v7
with:
name: build-artifact
path: |
package.json
package-lock.json
dist/
bin/
LICENSE

publish_npm:
runs-on: ubuntu-latest
needs: build
steps:
- name: Checkout repository
uses: actions/checkout@v6

- name: Download build artifact
uses: actions/download-artifact@v8
with:
name: build-artifact
path: build-artifact

- name: Restore build artifact
run: cp -r build-artifact/* .

- name: Publish to npm
uses: JS-DevTools/npm-publish@v3
with:
token: ${{ secrets.NPM_TOKEN }}

# Emscripten still doesn't support deno
# publish_jsr:
# runs-on: ubuntu-latest
# needs: build
# permissions:
# contents: read
# id-token: write
# steps:
# - name: Download build artifact
# uses: actions/download-artifact@v4
# with:
# name: build-artifact
# path: build-artifact

# - name: Restore build artifact
# run: cp -r build-artifact/* .

# - name: Generate jsr.json from package.json
# run: |
# node -e "const pkg = require('./package.json'); \
# const jsr = { \
# name: '@ceifa/' + pkg.name, \
# version: pkg.version, \
# exports: pkg.main, \
# include: ['dist/**/*', 'bin/**/*']
# }; \
# require('fs').writeFileSync('jsr.json', JSON.stringify(jsr, null, 2));"

# - name: Publish to JSR
# run: npx jsr publish
8 changes: 4 additions & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ jobs:

strategy:
matrix:
node-version: [18, 20, 22]
node-version: [22, 24]

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
submodules: recursive
- uses: mymindstorm/setup-emsdk@v12
- uses: mymindstorm/setup-emsdk@v14
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
Expand Down
4 changes: 3 additions & 1 deletion .prettierrc → .oxfmtrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,7 @@
"quoteProps": "consistent",
"semi": false,
"printWidth": 140,
"tabWidth": 4
"tabWidth": 4,
"sortPackageJson": false,
"ignorePatterns": ["rolldown.config.*.js"]
}
13 changes: 13 additions & 0 deletions .oxlintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"plugins": ["import", "promise", "node"],
"rules": {
"no-shadow": "off",
"no-extend-native": "off",
"consistent-function-scoping": "off",
"promise/always-return": "off"
},
"ignorePatterns": ["dist/**", "build/**", "rolldown.config.ts", "rolldown.config.*.js", "utils/**"],
"env": {
"builtin": true
}
}
17 changes: 9 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,26 +74,27 @@ $: ./sum.lua 10 30

## When to use wasmoon and fengari

Wasmoon compiles the [official Lua code](https://github.com/lua/lua) to webassembly and creates an abstraction layer to interop between Lua and JS, instead of [fengari](https://github.com/fengari-lua/fengari), that is an entire Lua VM rewritten in JS.
Wasmoon compiles the [official Lua code](https://github.com/lua/lua) to WebAssembly and creates an abstraction layer to interop between Lua and JS, instead of [fengari](https://github.com/fengari-lua/fengari), which is an entire Lua VM rewritten in JS.

### Performance

Because of wasm, wasmoon will run Lua code much faster than fengari, but if you are going to interop a lot between JS and Lua, this may be not be true anymore, you probably should test on you specific use case to take the prove.
Because of WebAssembly, wasmoon runs Lua code significantly faster than fengari. The table below shows results from a [heap sort benchmark](https://github.com/ceifa/wasmoon/blob/main/bench/heapsort.lua) sorting a list of 2,000 numbers (100 iterations, 5 warmup):

This is the results running a [heap sort code](https://github.com/ceifa/wasmoon/blob/main/bench/heapsort.lua) in a list of 2k numbers 10x(less is better):
| | avg | median | min | max | stddev | relative |
| ---------------- | ---------- | ---------- | ---------- | ---------- | --------- | -------- |
| **Wasmoon** | 13.41 ms | 13.07 ms | 12.20 ms | 16.23 ms | 1.12 ms | 1.00x |
| **Fengari** | 137.36 ms | 138.51 ms | 119.70 ms | 165.54 ms | 11.16 ms | 10.24x |

| wasmoon | fengari |
| -------- | --------- |
| 15.267ms | 389.923ms |
Wasmoon is **~10x faster** than fengari for pure Lua execution. If your use case involves heavy interop between JS and Lua, the difference may be smaller, benchmark your specific scenario.

### Size

Fengari is smaller than wasmoon, which can improve the user experience if in web environments:

| | wasmoon | fengari |
| ----------- | ------- | ------- |
| **plain** | 393kB | 214kB |
| **gzipped** | 130kB | 69kB |
| **plain** | 357kB | 211kB |
| **gzipped** | 123kB | 69kB |

## Fixing common errors on web environment

Expand Down
64 changes: 43 additions & 21 deletions bench/comparisons.js
Original file line number Diff line number Diff line change
@@ -1,30 +1,52 @@
const { readFileSync } = require('fs')
const path = require('path')
import { Lua } from '../dist/index.js'
import fengari from 'fengari'
import { isMainModule, parseBenchOptions, readBenchAsset, runBenchmarks } from './utils.js'

const fengari = require('fengari')
const wasmoon = require('../dist/index')
const heapsort = readBenchAsset('heapsort.lua')

const heapsort = readFileSync(path.resolve(__dirname, 'heapsort.lua'), 'utf-8')

const startFengari = () => {
function runFengariIteration() {
const state = fengari.lauxlib.luaL_newstate()
fengari.lualib.luaL_openlibs(state)
try {
fengari.lualib.luaL_openlibs(state)
assertStatus(fengari.lauxlib.luaL_loadstring(state, fengari.to_luastring(heapsort)), 'Fengari load')
assertStatus(fengari.lua.lua_pcallk(state, 0, 1, 0, 0, null), 'Fengari compile')
assertStatus(fengari.lua.lua_pcallk(state, 0, 1, 0, 0, null), 'Fengari execute')
} finally {
fengari.lua.lua_close(state)
}
}

console.time('Fengari')
fengari.lauxlib.luaL_loadstring(state, fengari.to_luastring(heapsort))
fengari.lua.lua_callk(state, 0, 1, 0, null)
fengari.lua.lua_callk(state, 0, 0, 0, null)
console.timeEnd('Fengari')
function createWasmoonIteration(lua) {
return function runWasmoonIteration() {
const state = lua.createState()
try {
assertStatus(state.global.lua.luaL_loadstring(state.global.address, heapsort), 'Wasmoon load')
assertStatus(state.global.lua.lua_pcallk(state.global.address, 0, 1, 0, 0, null), 'Wasmoon compile')
assertStatus(state.global.lua.lua_pcallk(state.global.address, 0, 1, 0, 0, null), 'Wasmoon execute')
} finally {
state.global.close()
}
}
}

const startWasmoon = async () => {
const state = await new wasmoon.LuaFactory().createEngine()
function assertStatus(status, label) {
if (status !== 0) {
throw new Error(`${label} failed with status ${status}`)
}
}

console.time('Wasmoon')
state.global.lua.luaL_loadstring(state.global.address, heapsort)
state.global.lua.lua_callk(state.global.address, 0, 1, 0, null)
state.global.lua.lua_callk(state.global.address, 0, 0, 0, null)
console.timeEnd('Wasmoon')
export async function runComparisonBench(options = {}) {
const lua = await Lua.load()
return runBenchmarks({
title: 'Comparison benchmarks',
benches: [
{ name: 'Fengari heapsort', run: runFengariIteration },
{ name: 'Wasmoon heapsort', run: createWasmoonIteration(lua) },
],
options,
})
}

Promise.resolve().then(startFengari).then(startWasmoon).catch(console.error)
if (isMainModule(import.meta.url)) {
await runComparisonBench(parseBenchOptions())
}
2 changes: 2 additions & 0 deletions bench/heapsort.lua
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,6 @@ return function()
assert(a[i] <= a[i + 1])
end
end

return Num
end
20 changes: 20 additions & 0 deletions bench/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { runComparisonBench } from './comparisons.js'
import { runStepBench } from './steps.js'
import { parseBenchOptions, printArtifactSizes, printBenchUsage } from './utils.js'

const options = parseBenchOptions()

if (options.help) {
printBenchUsage()
process.exit(0)
}

printArtifactSizes()

if (options.suite === 'all' || options.suite === 'steps') {
await runStepBench(options)
}

if (options.suite === 'all' || options.suite === 'comparisons') {
await runComparisonBench(options)
}
Loading
Loading