diff --git a/packages/web/README.md b/packages/web/README.md
new file mode 100644
index 00000000..c70a5df7
--- /dev/null
+++ b/packages/web/README.md
@@ -0,0 +1,35 @@
+# @strudel/web
+
+This package provides an easy to use bundle of multiple strudel packages for the web.
+
+## Usage
+
+```js
+import { repl } from '@strudel/web';
+
+const strudel = repl();
+
+document.getElementById('play').addEventListener('click',
+ () => strudel.evaluate('note("c a f e").jux(rev)')
+);
+```
+
+Note: Due to the [Autoplay policy](https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API/Best_practices#autoplay_policy), you can only play audio in a browser after a click event.
+
+### Loading samples
+
+By default, no external samples are loaded, but you can add them like this:
+
+```js
+import { repl, samples } from '@strudel/web';
+
+const strudel = repl({
+ prebake: () => samples('github:tidalcycles/Dirt-Samples/master'),
+});
+
+document.getElementById('play').addEventListener('click',
+ () => strudel.evaluate('s("bd,jvbass(3,8)").jux(rev)')
+);
+```
+
+You can learn [more about the `samples` function here](https://strudel.tidalcycles.org/learn/samples#loading-custom-samples).
diff --git a/packages/web/examples/repl-example/.gitignore b/packages/web/examples/repl-example/.gitignore
new file mode 100644
index 00000000..a547bf36
--- /dev/null
+++ b/packages/web/examples/repl-example/.gitignore
@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/packages/web/examples/repl-example/index.html b/packages/web/examples/repl-example/index.html
new file mode 100644
index 00000000..a7954e64
--- /dev/null
+++ b/packages/web/examples/repl-example/index.html
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+ @strudel/web REPL Example
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/web/examples/repl-example/package.json b/packages/web/examples/repl-example/package.json
new file mode 100644
index 00000000..42fb8b83
--- /dev/null
+++ b/packages/web/examples/repl-example/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "repl-example",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "license": "AGPL-3.0-or-later",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "preview": "vite preview"
+ },
+ "devDependencies": {
+ "vite": "^4.3.2"
+ },
+ "dependencies": {
+ "@strudel/web": "workspace:*"
+ }
+}
diff --git a/packages/web/package.json b/packages/web/package.json
new file mode 100644
index 00000000..11e9153e
--- /dev/null
+++ b/packages/web/package.json
@@ -0,0 +1,46 @@
+{
+ "name": "@strudel/web",
+ "version": "0.8.0",
+ "description": "Easy to setup, opiniated bundle of Strudel for the browser.",
+ "main": "repl.mjs",
+ "publishConfig": {
+ "main": "dist/index.js",
+ "module": "dist/index.mjs"
+ },
+ "scripts": {
+ "build": "vite build",
+ "prepublishOnly": "npm run build"
+ },
+ "type": "module",
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/tidalcycles/strudel.git"
+ },
+ "keywords": [
+ "tidalcycles",
+ "strudel",
+ "pattern",
+ "livecoding",
+ "algorave"
+ ],
+ "author": "Felix Roos ",
+ "contributors": [
+ "Alex McLean "
+ ],
+ "license": "AGPL-3.0-or-later",
+ "bugs": {
+ "url": "https://github.com/tidalcycles/strudel/issues"
+ },
+ "homepage": "https://github.com/tidalcycles/strudel#readme",
+ "dependencies": {
+ "@strudel.cycles/core": "workspace:*",
+ "@strudel.cycles/webaudio": "workspace:*",
+ "@strudel.cycles/soundfonts": "workspace:*",
+ "@strudel.cycles/mini": "workspace:*",
+ "@strudel.cycles/tonal": "workspace:*",
+ "@strudel.cycles/transpiler": "workspace:*"
+ },
+ "devDependencies": {
+ "vite": "^4.3.3"
+ }
+}
diff --git a/packages/web/repl.mjs b/packages/web/repl.mjs
new file mode 100644
index 00000000..f86459b1
--- /dev/null
+++ b/packages/web/repl.mjs
@@ -0,0 +1,38 @@
+export * from '@strudel.cycles/core';
+export * from '@strudel.cycles/webaudio';
+export * from '@strudel.cycles/soundfonts';
+export * from '@strudel.cycles/transpiler';
+export * from '@strudel.cycles/mini';
+export * from '@strudel.cycles/tonal';
+export * from '@strudel.cycles/webaudio';
+import { repl as _repl, evalScope, controls } from '@strudel.cycles/core';
+import { initAudioOnFirstClick, getAudioContext, registerSynthSounds, webaudioOutput } from '@strudel.cycles/webaudio';
+import { registerSoundfonts } from '@strudel.cycles/soundfonts';
+import { transpiler } from '@strudel.cycles/transpiler';
+
+async function prebake(userPrebake) {
+ const loadModules = evalScope(
+ evalScope,
+ controls,
+ import('@strudel.cycles/core'),
+ import('@strudel.cycles/mini'),
+ import('@strudel.cycles/tonal'),
+ import('@strudel.cycles/webaudio'),
+ );
+ await Promise.all([loadModules, registerSynthSounds(), registerSoundfonts(), userPrebake?.()]);
+}
+
+export function repl(options = {}) {
+ const prebaked = prebake(options?.prebake);
+ initAudioOnFirstClick();
+ return _repl({
+ defaultOutput: webaudioOutput,
+ getTime: () => getAudioContext().currentTime,
+ transpiler,
+ ...options,
+ beforeEval: async (args) => {
+ options?.beforeEval?.(args);
+ await prebaked;
+ },
+ });
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 596351fa..ee27ce0d 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -471,6 +471,41 @@ importers:
specifier: ^0.28.0
version: 0.28.0(@vitest/ui@0.28.0)
+ packages/web:
+ dependencies:
+ '@strudel.cycles/core':
+ specifier: workspace:*
+ version: link:../core
+ '@strudel.cycles/mini':
+ specifier: workspace:*
+ version: link:../mini
+ '@strudel.cycles/soundfonts':
+ specifier: workspace:*
+ version: link:../soundfonts
+ '@strudel.cycles/tonal':
+ specifier: workspace:*
+ version: link:../tonal
+ '@strudel.cycles/transpiler':
+ specifier: workspace:*
+ version: link:../transpiler
+ '@strudel.cycles/webaudio':
+ specifier: workspace:*
+ version: link:../webaudio
+ devDependencies:
+ vite:
+ specifier: ^4.3.3
+ version: 4.3.3(@types/node@18.16.3)
+
+ packages/web/examples/repl-example:
+ dependencies:
+ '@strudel/web':
+ specifier: workspace:*
+ version: link:../..
+ devDependencies:
+ vite:
+ specifier: ^4.3.2
+ version: 4.3.3(@types/node@18.16.3)
+
packages/webaudio:
dependencies:
'@strudel.cycles/core':
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index 4b821d33..984f2447 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -4,4 +4,5 @@ packages:
- "website/"
- "packages/core/examples/vite-vanilla-repl"
- "packages/core/examples/vite-vanilla-repl-cm6"
- - "packages/react/examples/nano-repl"
\ No newline at end of file
+ - "packages/react/examples/nano-repl"
+ - "packages/web/examples/repl-example"