ruwi 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.cursor/rules/ruby_comments.mdc +29 -0
- data/.github/workflows/playwright.yml +74 -0
- data/.github/workflows/rspec.yml +31 -0
- data/.node-version +1 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/LICENSE.txt +21 -0
- data/Makefile +56 -0
- data/README.md +237 -0
- data/Rakefile +4 -0
- data/docs/conditional-rendering.md +119 -0
- data/docs/lifecycle-hooks.md +75 -0
- data/docs/list-rendering.md +51 -0
- data/examples/.gitignore +4 -0
- data/examples/Gemfile +5 -0
- data/examples/Gemfile.lock +39 -0
- data/examples/Makefile +15 -0
- data/examples/npm-packages/runtime/counter/index.html +28 -0
- data/examples/npm-packages/runtime/counter/index.rb +62 -0
- data/examples/npm-packages/runtime/counter/index.spec.js +42 -0
- data/examples/npm-packages/runtime/hello/index.html +28 -0
- data/examples/npm-packages/runtime/hello/index.rb +29 -0
- data/examples/npm-packages/runtime/hello/index.spec.js +53 -0
- data/examples/npm-packages/runtime/input/index.html +28 -0
- data/examples/npm-packages/runtime/input/index.rb +46 -0
- data/examples/npm-packages/runtime/input/index.spec.js +58 -0
- data/examples/npm-packages/runtime/list/index.html +27 -0
- data/examples/npm-packages/runtime/list/index.rb +33 -0
- data/examples/npm-packages/runtime/list/index.spec.js +46 -0
- data/examples/npm-packages/runtime/on_mounted_demo/index.html +40 -0
- data/examples/npm-packages/runtime/on_mounted_demo/index.rb +59 -0
- data/examples/npm-packages/runtime/on_mounted_demo/index.spec.js +50 -0
- data/examples/npm-packages/runtime/r_if_attribute_demo/index.html +34 -0
- data/examples/npm-packages/runtime/r_if_attribute_demo/index.rb +113 -0
- data/examples/npm-packages/runtime/r_if_attribute_demo/index.spec.js +140 -0
- data/examples/npm-packages/runtime/random_cocktail/index.html +27 -0
- data/examples/npm-packages/runtime/random_cocktail/index.rb +69 -0
- data/examples/npm-packages/runtime/random_cocktail/index.spec.js +101 -0
- data/examples/npm-packages/runtime/search_field/index.html +27 -0
- data/examples/npm-packages/runtime/search_field/index.rb +39 -0
- data/examples/npm-packages/runtime/search_field/index.spec.js +59 -0
- data/examples/npm-packages/runtime/todos/index.html +28 -0
- data/examples/npm-packages/runtime/todos/index.rb +239 -0
- data/examples/npm-packages/runtime/todos/index.spec.js +161 -0
- data/examples/npm-packages/runtime/todos/todos_repository.rb +23 -0
- data/examples/package.json +12 -0
- data/examples/src/counter/index.html +23 -0
- data/examples/src/counter/index.rb +60 -0
- data/examples/src/index.html +21 -0
- data/examples/src/index.rb +26 -0
- data/examples/src/todos/index.html +23 -0
- data/examples/src/todos/index.rb +237 -0
- data/examples/src/todos/todos_repository.rb +23 -0
- data/exe/ruwi +6 -0
- data/lib/ruwi/cli/command/base.rb +192 -0
- data/lib/ruwi/cli/command/dev.rb +207 -0
- data/lib/ruwi/cli/command/pack.rb +36 -0
- data/lib/ruwi/cli/command/rebuild.rb +38 -0
- data/lib/ruwi/cli/command/setup.rb +159 -0
- data/lib/ruwi/cli/command.rb +48 -0
- data/lib/ruwi/runtime/app.rb +53 -0
- data/lib/ruwi/runtime/component.rb +215 -0
- data/lib/ruwi/runtime/dispatcher.rb +46 -0
- data/lib/ruwi/runtime/dom/attributes.rb +105 -0
- data/lib/ruwi/runtime/dom/destroy_dom.rb +63 -0
- data/lib/ruwi/runtime/dom/events.rb +40 -0
- data/lib/ruwi/runtime/dom/mount_dom.rb +108 -0
- data/lib/ruwi/runtime/dom/patch_dom.rb +237 -0
- data/lib/ruwi/runtime/dom/scheduler.rb +51 -0
- data/lib/ruwi/runtime/dom.rb +13 -0
- data/lib/ruwi/runtime/nodes_equal.rb +45 -0
- data/lib/ruwi/runtime/template/build_conditional_group.rb +150 -0
- data/lib/ruwi/runtime/template/build_for_group.rb +125 -0
- data/lib/ruwi/runtime/template/build_vdom.rb +220 -0
- data/lib/ruwi/runtime/template/parser.rb +134 -0
- data/lib/ruwi/runtime/template.rb +11 -0
- data/lib/ruwi/runtime/utils/arrays.rb +185 -0
- data/lib/ruwi/runtime/utils/objects.rb +37 -0
- data/lib/ruwi/runtime/utils/props.rb +25 -0
- data/lib/ruwi/runtime/utils/strings.rb +19 -0
- data/lib/ruwi/runtime/utils.rb +11 -0
- data/lib/ruwi/runtime/vdom.rb +84 -0
- data/lib/ruwi/version.rb +5 -0
- data/lib/ruwi.rb +14 -0
- data/package-lock.json +73 -0
- data/package.json +32 -0
- data/packages/npm-packages/runtime/README.md +5 -0
- data/packages/npm-packages/runtime/eslint.config.mjs +16 -0
- data/packages/npm-packages/runtime/package-lock.json +6668 -0
- data/packages/npm-packages/runtime/package.json +38 -0
- data/packages/npm-packages/runtime/rollup.config.mjs +147 -0
- data/packages/npm-packages/runtime/src/__tests__/sample.test.js +5 -0
- data/packages/npm-packages/runtime/src/index.js +37 -0
- data/packages/npm-packages/runtime/src/ruwi +1 -0
- data/packages/npm-packages/runtime/src/ruwi.rb +1 -0
- data/packages/npm-packages/runtime/vitest.config.js +8 -0
- data/playwright.config.js +78 -0
- data/sig/ruwi.rbs +4 -0
- data/spec/ruwi/cli/command/base_spec.rb +503 -0
- data/spec/ruwi/cli/command/dev_spec.rb +442 -0
- data/spec/ruwi/cli/command/pack_spec.rb +131 -0
- data/spec/ruwi/cli/command/rebuild_spec.rb +95 -0
- data/spec/ruwi/cli/command/setup_spec.rb +251 -0
- data/spec/ruwi/cli/command_spec.rb +118 -0
- data/spec/ruwi/runtime/component_spec.rb +416 -0
- data/spec/ruwi/runtime/dom/scheduler_spec.rb +98 -0
- data/spec/ruwi/runtime/nodes_equal_spec.rb +190 -0
- data/spec/ruwi/runtime/template/build_conditional_group_spec.rb +505 -0
- data/spec/ruwi/runtime/template/build_for_group_spec.rb +377 -0
- data/spec/ruwi/runtime/template/build_vdom_spec.rb +573 -0
- data/spec/ruwi/runtime/template/parser_spec.rb +627 -0
- data/spec/ruwi/runtime/utils/arrays_spec.rb +228 -0
- data/spec/ruwi/runtime/utils/objects_spec.rb +127 -0
- data/spec/ruwi/runtime/utils/props_spec.rb +205 -0
- data/spec/ruwi/runtime/utils/strings_spec.rb +107 -0
- data/spec/spec_helper.rb +16 -0
- metadata +229 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ruwi",
|
|
3
|
+
"version": "0.10.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "dist/ruwi.js",
|
|
6
|
+
"files": [
|
|
7
|
+
"dist/ruwi.js",
|
|
8
|
+
"dist/ruwi.rb",
|
|
9
|
+
"dist/ruwi/**/*.rb"
|
|
10
|
+
],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"prepack": "npm run build",
|
|
13
|
+
"build": "NODE_ENV=production rollup -c",
|
|
14
|
+
"build:dev": "NODE_ENV=development rollup -c",
|
|
15
|
+
"lint": "eslint src",
|
|
16
|
+
"lint:fix": "eslint src --fix",
|
|
17
|
+
"test": "vitest",
|
|
18
|
+
"test:run": "vitest run"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"ruby",
|
|
22
|
+
"wasm",
|
|
23
|
+
"ui"
|
|
24
|
+
],
|
|
25
|
+
"author": "t0yohei <k.t0yohei@gmail.com>",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"type": "module",
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@rollup/plugin-replace": "^6.0.2",
|
|
30
|
+
"eslint": "^9.25.1",
|
|
31
|
+
"jsdom": "^26.1.0",
|
|
32
|
+
"rollup": "^4.40.0",
|
|
33
|
+
"rollup-plugin-cleanup": "^3.2.1",
|
|
34
|
+
"rollup-plugin-copy": "^3.5.0",
|
|
35
|
+
"rollup-plugin-filesize": "^10.0.0",
|
|
36
|
+
"vitest": "^3.1.2"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import copy from "rollup-plugin-copy";
|
|
2
|
+
import cleanup from "rollup-plugin-cleanup";
|
|
3
|
+
import filesize from "rollup-plugin-filesize";
|
|
4
|
+
import replace from "@rollup/plugin-replace";
|
|
5
|
+
import { glob } from "glob";
|
|
6
|
+
import process from "process";
|
|
7
|
+
import {
|
|
8
|
+
readFileSync,
|
|
9
|
+
writeFileSync,
|
|
10
|
+
lstatSync,
|
|
11
|
+
unlinkSync,
|
|
12
|
+
realpathSync,
|
|
13
|
+
} from "fs";
|
|
14
|
+
import { join, dirname } from "path";
|
|
15
|
+
import { fileURLToPath } from "url";
|
|
16
|
+
|
|
17
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
18
|
+
|
|
19
|
+
// Find all Ruby files in ruwi directory
|
|
20
|
+
const rubyFiles = glob
|
|
21
|
+
.sync("src/ruwi/runtime/**/*.rb")
|
|
22
|
+
.map((file) => file.replace("src/", ""))
|
|
23
|
+
.sort((a, b) => {
|
|
24
|
+
// Files in root directory should be loaded first
|
|
25
|
+
const aIsRoot = !a.includes("/");
|
|
26
|
+
const bIsRoot = !b.includes("/");
|
|
27
|
+
if (aIsRoot && !bIsRoot) return -1;
|
|
28
|
+
if (!aIsRoot && bIsRoot) return 1;
|
|
29
|
+
|
|
30
|
+
// Then sort by directory and filename
|
|
31
|
+
return a.localeCompare(b);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// Determine environment based on NODE_ENV
|
|
35
|
+
const isDevelopment = process.env.NODE_ENV === "development";
|
|
36
|
+
|
|
37
|
+
// Plugin to clean up existing symlinks in dist directory before copy
|
|
38
|
+
const cleanupSymlinks = () => {
|
|
39
|
+
return {
|
|
40
|
+
name: "cleanup-symlinks",
|
|
41
|
+
buildStart() {
|
|
42
|
+
const distRubyFilePath = join(__dirname, "dist/ruwi.rb");
|
|
43
|
+
try {
|
|
44
|
+
const stats = lstatSync(distRubyFilePath);
|
|
45
|
+
if (stats.isSymbolicLink() || stats.isFile()) {
|
|
46
|
+
// Remove existing file/symlink before copy plugin runs
|
|
47
|
+
unlinkSync(distRubyFilePath);
|
|
48
|
+
}
|
|
49
|
+
} catch (e) {
|
|
50
|
+
// Ignore errors (file might not exist)
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// Plugin to remove require_relative lines from Ruby files
|
|
57
|
+
const removeRequireRelative = () => {
|
|
58
|
+
return {
|
|
59
|
+
name: "remove-require-relative",
|
|
60
|
+
writeBundle() {
|
|
61
|
+
// Process all Ruby files in dist directory (including dist/ruwi.rb)
|
|
62
|
+
// Ensure we only process files in dist/ directory, not lib/ or other source directories
|
|
63
|
+
const distDir = join(__dirname, "dist");
|
|
64
|
+
const distRubyFiles = glob.sync("dist/**/*.rb", {
|
|
65
|
+
cwd: __dirname,
|
|
66
|
+
absolute: false,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
distRubyFiles.forEach((file) => {
|
|
70
|
+
const filePath = join(__dirname, file);
|
|
71
|
+
try {
|
|
72
|
+
const stats = lstatSync(filePath);
|
|
73
|
+
let content;
|
|
74
|
+
// If it's a symlink to lib/, read from lib but write to dist
|
|
75
|
+
if (stats.isSymbolicLink()) {
|
|
76
|
+
const resolvedPath = realpathSync(filePath);
|
|
77
|
+
const distDirResolved = realpathSync(distDir);
|
|
78
|
+
if (!resolvedPath.startsWith(distDirResolved)) {
|
|
79
|
+
// Read from lib, but write to dist (replacing the symlink)
|
|
80
|
+
content = readFileSync(resolvedPath, "utf-8");
|
|
81
|
+
unlinkSync(filePath);
|
|
82
|
+
} else {
|
|
83
|
+
// Symlink resolves within dist, read resolved path
|
|
84
|
+
content = readFileSync(resolvedPath, "utf-8");
|
|
85
|
+
}
|
|
86
|
+
} else {
|
|
87
|
+
// Regular file, read directly
|
|
88
|
+
content = readFileSync(filePath, "utf-8");
|
|
89
|
+
}
|
|
90
|
+
// Remove lines that start with require_relative (with optional whitespace)
|
|
91
|
+
content = content.replace(/^\s*require_relative\s+.*$/gm, "");
|
|
92
|
+
// Remove multiple consecutive empty lines
|
|
93
|
+
content = content.replace(/\n{3,}/g, "\n\n");
|
|
94
|
+
writeFileSync(filePath, content, "utf-8");
|
|
95
|
+
} catch (e) {
|
|
96
|
+
// If read/write fails, skip this file
|
|
97
|
+
console.warn(`Could not process file ${filePath}: ${e.message}`);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export default {
|
|
105
|
+
input: "src/index.js",
|
|
106
|
+
output: {
|
|
107
|
+
file: "dist/ruwi.js",
|
|
108
|
+
format: "esm",
|
|
109
|
+
sourcemap: true,
|
|
110
|
+
},
|
|
111
|
+
plugins: [
|
|
112
|
+
replace({
|
|
113
|
+
preventAssignment: true,
|
|
114
|
+
values: {
|
|
115
|
+
"window.RUWI_ENV": JSON.stringify(
|
|
116
|
+
isDevelopment ? "development" : "production"
|
|
117
|
+
),
|
|
118
|
+
"window.RUWI_FILES": JSON.stringify(rubyFiles),
|
|
119
|
+
},
|
|
120
|
+
}),
|
|
121
|
+
cleanupSymlinks(),
|
|
122
|
+
copy({
|
|
123
|
+
targets: [
|
|
124
|
+
{
|
|
125
|
+
src: "src/ruwi/**/*",
|
|
126
|
+
dest: "dist/ruwi",
|
|
127
|
+
flatten: false,
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
src: "src/ruwi.rb",
|
|
131
|
+
dest: "dist",
|
|
132
|
+
},
|
|
133
|
+
],
|
|
134
|
+
// Resolve symbolic links when copying to ensure dist files are regular files
|
|
135
|
+
// This allows us to process dist/ruwi.rb without affecting lib/ruwi.rb
|
|
136
|
+
copySyncOptions: {
|
|
137
|
+
dereference: true,
|
|
138
|
+
},
|
|
139
|
+
}),
|
|
140
|
+
cleanup({
|
|
141
|
+
comments: "none",
|
|
142
|
+
extensions: ["js"],
|
|
143
|
+
}),
|
|
144
|
+
removeRequireRelative(),
|
|
145
|
+
filesize(),
|
|
146
|
+
],
|
|
147
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const userDefinedRubyScript = document.querySelector(
|
|
2
|
+
"script[type='text/ruby']"
|
|
3
|
+
);
|
|
4
|
+
|
|
5
|
+
const scriptElement = document.createElement("script");
|
|
6
|
+
scriptElement.src =
|
|
7
|
+
"https://cdn.jsdelivr.net/npm/@ruby/3.4-wasm-wasi@latest/dist/browser.script.iife.js";
|
|
8
|
+
scriptElement.setAttribute("defer", "");
|
|
9
|
+
userDefinedRubyScript.before(scriptElement);
|
|
10
|
+
|
|
11
|
+
function loadRubyScript(filePath) {
|
|
12
|
+
let baseUrl;
|
|
13
|
+
if (window.RUWI_ENV === "production") {
|
|
14
|
+
baseUrl = "https://unpkg.com/ruwi@latest/dist";
|
|
15
|
+
} else {
|
|
16
|
+
baseUrl = "../../../../packages/npm-packages/runtime/dist";
|
|
17
|
+
}
|
|
18
|
+
let rubyScriptElement = document.createElement("script");
|
|
19
|
+
rubyScriptElement.type = "text/ruby";
|
|
20
|
+
rubyScriptElement.charset = "utf-8";
|
|
21
|
+
rubyScriptElement.src = `${baseUrl}/${filePath}`;
|
|
22
|
+
rubyScriptElement.setAttribute("defer", "");
|
|
23
|
+
userDefinedRubyScript.before(rubyScriptElement);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Load ruwi.rb
|
|
27
|
+
loadRubyScript("ruwi.rb");
|
|
28
|
+
|
|
29
|
+
// Load all Ruby files in ruwi directory
|
|
30
|
+
const rubyFiles = window.RUWI_FILES;
|
|
31
|
+
if (rubyFiles === undefined) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
"RUWI_FILES is not defined. This file should be built with rollup."
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
rubyFiles.forEach((file) => loadRubyScript(file));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
../../../../lib/ruwi
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
../../../../lib/ruwi.rb
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { defineConfig, devices } from "@playwright/test";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @see https://playwright.dev/docs/test-configuration
|
|
5
|
+
*/
|
|
6
|
+
export default defineConfig({
|
|
7
|
+
testDir: "./examples/npm-packages/runtime",
|
|
8
|
+
testMatch: "*/*.spec.js",
|
|
9
|
+
/* Run tests in files in parallel */
|
|
10
|
+
fullyParallel: true,
|
|
11
|
+
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
|
12
|
+
forbidOnly: !!process.env.CI,
|
|
13
|
+
/* Retry on CI only */
|
|
14
|
+
retries: process.env.CI ? 2 : 1,
|
|
15
|
+
/* Opt out of parallel tests on CI. */
|
|
16
|
+
workers: process.env.CI ? 2 : undefined,
|
|
17
|
+
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
|
18
|
+
reporter: [["html", { open: "never" }]],
|
|
19
|
+
/* Output directory for test results */
|
|
20
|
+
outputDir: "test-results/",
|
|
21
|
+
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
|
22
|
+
use: {
|
|
23
|
+
/* Base URL to use in actions like `await page.goto('/')`. */
|
|
24
|
+
baseURL: "http://localhost:8080",
|
|
25
|
+
|
|
26
|
+
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
|
27
|
+
trace: "on-first-retry",
|
|
28
|
+
|
|
29
|
+
/* Take screenshot on failure */
|
|
30
|
+
screenshot: "only-on-failure",
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
/* Configure projects for major browsers */
|
|
34
|
+
projects: [
|
|
35
|
+
{
|
|
36
|
+
name: "chromium",
|
|
37
|
+
use: { ...devices["Desktop Chrome"] },
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
// {
|
|
41
|
+
// name: "firefox",
|
|
42
|
+
// use: { ...devices["Desktop Firefox"] },
|
|
43
|
+
// },
|
|
44
|
+
|
|
45
|
+
// {
|
|
46
|
+
// name: "webkit",
|
|
47
|
+
// use: { ...devices["Desktop Safari"] },
|
|
48
|
+
// },
|
|
49
|
+
|
|
50
|
+
/* Test against mobile viewports. */
|
|
51
|
+
// {
|
|
52
|
+
// name: 'Mobile Chrome',
|
|
53
|
+
// use: { ...devices['Pixel 5'] },
|
|
54
|
+
// },
|
|
55
|
+
// {
|
|
56
|
+
// name: 'Mobile Safari',
|
|
57
|
+
// use: { ...devices['iPhone 12'] },
|
|
58
|
+
// },
|
|
59
|
+
|
|
60
|
+
/* Test against branded browsers. */
|
|
61
|
+
// {
|
|
62
|
+
// name: 'Microsoft Edge',
|
|
63
|
+
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
|
|
64
|
+
// },
|
|
65
|
+
// {
|
|
66
|
+
// name: 'Google Chrome',
|
|
67
|
+
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
|
|
68
|
+
// },
|
|
69
|
+
],
|
|
70
|
+
|
|
71
|
+
/* Run your local dev server before starting the tests */
|
|
72
|
+
webServer: {
|
|
73
|
+
command: "npm run serve:examples:test",
|
|
74
|
+
url: "http://localhost:8080",
|
|
75
|
+
reuseExistingServer: !process.env.CI,
|
|
76
|
+
timeout: 120 * 1000, // 2 minutes timeout for Ruby WASM to load
|
|
77
|
+
},
|
|
78
|
+
});
|
data/sig/ruwi.rbs
ADDED