├── .babelrc.js
├── .browserslistrc
├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .github
└── workflows
│ └── publish.yml
├── .gitignore
├── .husky
└── pre-push
├── .npmrc
├── LICENSE
├── README.md
├── package.json
├── pnpm-lock.yaml
├── rollup.config.mjs
├── src
├── global.d.ts
├── index.ts
└── solid.ts
└── tsconfig.json
/.babelrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: require.resolve('@gera2ld/plaid/config/babelrc-base'),
3 | presets: ['@babel/preset-typescript'],
4 | };
5 |
--------------------------------------------------------------------------------
/.browserslistrc:
--------------------------------------------------------------------------------
1 | Chrome >= 55
2 | Firefox >= 53
3 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 | quote_type = single
12 |
13 | [*.md]
14 | trim_trailing_whitespace = false
15 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | /*
2 | !/src
3 | !/test
4 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | extends: [
4 | require.resolve('@gera2ld/plaid/eslint'),
5 | ],
6 | parserOptions: {
7 | project: './tsconfig.json',
8 | },
9 | };
10 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: Publish to npmjs
2 |
3 | on:
4 | push:
5 | tags:
6 | - v*
7 |
8 | jobs:
9 | build:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: actions/checkout@v4
13 |
14 | - uses: pnpm/action-setup@v4
15 | name: Install pnpm
16 | with:
17 | version: 9
18 | run_install: false
19 |
20 | - uses: actions/setup-node@v4
21 | with:
22 | node-version: 22
23 | cache: 'pnpm'
24 | registry-url: 'https://registry.npmjs.org'
25 |
26 | - run: pnpm i && pnpm publish --no-git-checks
27 | env:
28 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
29 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | *.log
3 | /.idea
4 | /dist
5 | /.nyc_output
6 | /coverage
7 | /types
8 | /docs
9 |
--------------------------------------------------------------------------------
/.husky/pre-push:
--------------------------------------------------------------------------------
1 | npm run lint
2 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | shamefully-hoist = true
2 | strict-peer-dependencies = false
3 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Gerald
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # @violentmonkey/dom
2 |
3 | [](https://npm.im/@violentmonkey/dom)
4 | 
5 | [](https://www.jsdocs.io/package/@violentmonkey/dom)
6 |
7 | Use JSX for HTML elements.
8 |
9 | Based on [@gera2ld/jsx-dom](https://github.com/gera2ld/jsx-dom).
10 |
11 | ## What is it?
12 |
13 | This library is just a light wrapper around `document.createElement`. So we can easily create DOM elements using JSX with the help of this library instead of writing tedious imperative code.
14 |
15 | ## When should we NOT use it?
16 |
17 | You should NOT use it when you use a library that has its own implementation of JSX, such as React, Vue, Svelte, SolidJS, etc. The JSX syntaxes are incompatible and using them together will cause unexpected issues.
18 |
19 | You don't need it if you initialize a userscript project with [generator-userscript](https://github.com/violentmonkey/generator-userscript), which uses [solid-js](https://solidjs.com/).
20 |
21 | However, you can still use methods like `VM.h` directly **without using JSX** to make the code shorter.
22 |
23 | ## Usage
24 |
25 | First, include `@violentmonkey/dom` as a dependency:
26 |
27 | ```js
28 | // ...
29 | // @require https://cdn.jsdelivr.net/npm/@violentmonkey/dom@2
30 | // ...
31 | ```
32 |
33 | Then you can use `VM.h` (similar to `React.createElement`) and `VM.m` (for `mount`ing as DOM elements) directly.
34 |
35 | There is also a `VM.hm` for `VM.h` plus `VM.m` if you don't need SVG support and want to get rid of `VM.m`.
36 |
37 | ```js
38 | const vdom = VM.h('div', {}, 'hello');
39 | const el = VM.m(vdom); // -> HTMLDivElement
40 |
41 | // or
42 | const el = VM.hm('div', {}, 'hello'); // -> HTMLDivElement
43 |
44 | document.body.appendChild(el);
45 | ```
46 |
47 | Or use with JSX and bundlers, for example:
48 |
49 | ```js
50 | // .babelrc.js
51 | {
52 | plugins: [
53 | // JSX
54 | ['@babel/plugin-transform-react-jsx', {
55 | pragma: 'VM.h', // or 'VM.hm' if you don't need SVG support and want to get rid of 'VM.m'
56 | pragmaFrag: 'VM.Fragment',
57 | }],
58 | ],
59 | }
60 | ```
61 |
62 | ```js
63 | // pragma: VM.h
64 | document.body.appendChild(VM.m(
hello
));
65 |
66 | // pragma: VM.hm
67 | document.body.appendChild(hello
);
68 | ```
69 |
70 | ## API
71 |
72 | See [](https://www.jsdocs.io/package/@violentmonkey/dom).
73 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@violentmonkey/dom",
3 | "version": "2.2.1",
4 | "description": "Use JSX for HTML elements.",
5 | "author": "Gerald ",
6 | "license": "ISC",
7 | "repository": "git@github.com:violentmonkey/vm-dom.git",
8 | "scripts": {
9 | "prepare": "husky || true",
10 | "dev": "rollup -wc",
11 | "build:types": "tsc",
12 | "build:js": "rollup -c",
13 | "build": "run-s ci clean build:*",
14 | "format": "prettier --ignore-path .eslintignore --write .",
15 | "lint": "prettier --ignore-path .eslintignore --check . && eslint --ext .ts,tsx src",
16 | "prepublishOnly": "run-s build",
17 | "ci": "run-s lint",
18 | "clean": "del-cli dist types"
19 | },
20 | "publishConfig": {
21 | "access": "public",
22 | "registry": "https://registry.npmjs.org/"
23 | },
24 | "unpkg": "dist/index.js",
25 | "jsdelivr": "dist/index.js",
26 | "main": "dist/index.js",
27 | "module": "dist/index.mjs",
28 | "exports": {
29 | ".": {
30 | "import": "./dist/index.mjs",
31 | "default": "./dist/index.js",
32 | "types": "./types/index.d.ts"
33 | },
34 | "./solid-js": "./dist/solid.js"
35 | },
36 | "files": [
37 | "dist",
38 | "types"
39 | ],
40 | "typings": "types/index.d.ts",
41 | "devDependencies": {
42 | "@gera2ld/plaid": "~2.7.0",
43 | "@gera2ld/plaid-rollup": "~2.7.0",
44 | "del-cli": "^6.0.0",
45 | "husky": "^9.1.6"
46 | },
47 | "dependencies": {
48 | "@babel/runtime": "^7.25.9",
49 | "@gera2ld/jsx-dom": "^2.2.2",
50 | "solid-js": "^1.9.5"
51 | },
52 | "engines": {
53 | "node": ">=20"
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/rollup.config.mjs:
--------------------------------------------------------------------------------
1 | import { defineExternal, definePlugins } from '@gera2ld/plaid-rollup';
2 | import { defineConfig } from 'rollup';
3 | import solidPkg from 'solid-js/package.json' with { type: 'json' };
4 | import pkg from './package.json' with { type: 'json' };
5 |
6 | const external = defineExternal(Object.keys(pkg.dependencies));
7 | const bundleOptions = {
8 | extend: true,
9 | esModule: false,
10 | };
11 | const outputOptions = {
12 | indent: false,
13 | banner: `/*! ${pkg.name}@${pkg.version} | ${pkg.license} License */`,
14 | };
15 | const replaceValues = {
16 | 'process.env.VERSION': pkg.version,
17 | };
18 |
19 | export default defineConfig([
20 | {
21 | input: 'src/index.ts',
22 | plugins: definePlugins({
23 | replaceValues,
24 | }),
25 | external,
26 | output: {
27 | format: 'esm',
28 | file: `dist/index.mjs`,
29 | ...outputOptions,
30 | },
31 | },
32 | {
33 | input: 'src/index.ts',
34 | plugins: definePlugins({
35 | replaceValues,
36 | }),
37 | output: {
38 | format: 'iife',
39 | file: `dist/index.js`,
40 | name: 'VM',
41 | ...outputOptions,
42 | ...bundleOptions,
43 | },
44 | },
45 | {
46 | input: 'src/solid.ts',
47 | plugins: definePlugins({
48 | replaceValues,
49 | }),
50 | output: {
51 | format: 'iife',
52 | file: `dist/solid.js`,
53 | name: 'VM.solid',
54 | ...outputOptions,
55 | ...bundleOptions,
56 | banner: `/*! ${pkg.name}@${pkg.version}/solid-js solid-js@${solidPkg.version} | ${solidPkg.license} License */`,
57 | },
58 | },
59 | ]);
60 |
--------------------------------------------------------------------------------
/src/global.d.ts:
--------------------------------------------------------------------------------
1 | declare const VM: {
2 | versions: Record;
3 | };
4 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | h,
3 | createElement,
4 | Fragment,
5 | mountDom,
6 | mountDom as m,
7 | hm,
8 | } from '@gera2ld/jsx-dom';
9 |
10 | export const versions = Object.assign(
11 | (typeof VM !== 'undefined' && VM?.versions) || {},
12 | {
13 | dom: 'process.env.VERSION',
14 | },
15 | );
16 |
17 | /**
18 | * Return all elements that match the given `xpath` as an array.
19 | */
20 | export function getElementsByXPath(xpath: string, context: Node = document) {
21 | const iterator = document.evaluate(
22 | xpath,
23 | context,
24 | null,
25 | XPathResult.ANY_TYPE,
26 | null,
27 | );
28 | const result: Node[] = [];
29 | let item: Node;
30 | while ((item = iterator.iterateNext())) {
31 | result.push(item);
32 | }
33 | return result;
34 | }
35 |
36 | /**
37 | * Walk a node tree and return all text contents in an array.
38 | */
39 | export function getTextValues(node: HTMLElement) {
40 | if (node.nodeType === Node.TEXT_NODE) return [node.nodeValue];
41 | if (
42 | node.nodeType === Node.ELEMENT_NODE &&
43 | !['script', 'style'].includes(node.tagName.toLowerCase())
44 | ) {
45 | return Array.from(node.childNodes).flatMap(getTextValues);
46 | }
47 | return [];
48 | }
49 |
50 | /**
51 | * Observe an existing `node` until `callback` returns `true`.
52 | * The returned function can be called explicitly to disconnect the observer.
53 | *
54 | * ```js
55 | * VM.observe(document.body, () => {
56 | * const node = document.querySelector('.profile');
57 | * if (node) {
58 | * console.log('It\'s there!');
59 | * return true;
60 | * }
61 | * });
62 | * ```
63 | */
64 | export function observe(
65 | node: Node,
66 | callback: (
67 | mutations: MutationRecord[],
68 | observer: MutationObserver,
69 | ) => boolean | void,
70 | options?: MutationObserverInit,
71 | ): () => void {
72 | const observer = new MutationObserver((mutations, ob) => {
73 | const result = callback(mutations, ob);
74 | if (result) disconnect();
75 | });
76 | observer.observe(
77 | node,
78 | Object.assign(
79 | {
80 | childList: true,
81 | subtree: true,
82 | },
83 | options,
84 | ),
85 | );
86 | const disconnect = () => observer.disconnect();
87 | return disconnect;
88 | }
89 |
--------------------------------------------------------------------------------
/src/solid.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This is just a light wrapper for a UMD bundle of solid-js.
3 | * The size of userscripts can be significantly reduced by using a CDN hosted UMD bundle of solid-js.
4 | */
5 | export * from 'solid-js';
6 | export * as store from 'solid-js/store';
7 | export * as web from 'solid-js/web';
8 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es6",
4 | "module": "es6",
5 | "moduleResolution": "node",
6 | "declaration": true,
7 | "emitDeclarationOnly": true,
8 | "outDir": "types",
9 | "allowSyntheticDefaultImports": true,
10 | "jsx": "react",
11 | "lib": ["DOM", "ES6", "DOM.Iterable", "ScriptHost", "ESNext"]
12 | },
13 | "include": ["src/**/*.ts"]
14 | }
15 |
--------------------------------------------------------------------------------