├── .babelrc.js ├── .github ├── dependabot.yml └── workflows │ ├── buildTest.yml │ ├── codeql-analysis.yml │ ├── gpr-name.js │ └── npm-publish.yml ├── .gitignore ├── LICENSE ├── README.md ├── dev.user.js ├── dist ├── bundle.user.js ├── bundle.user.js.map ├── dev.user.js ├── release-1.2.4.user.js └── release-1.2.5.user.js ├── meta.json ├── package-lock.json ├── package.json ├── rollup.config.mjs ├── server.js ├── src ├── empty.ts ├── examplePlain.js ├── exampleReact.tsx ├── exampleTypedGM.ts └── index.js ├── tsconfig.json └── types └── GM └── index.d.ts /.babelrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | '@babel/preset-react' 5 | ] 6 | ], 7 | plugins: [ 8 | '@babel/plugin-proposal-class-properties' 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | # Maintain dependencies for npm 9 | - package-ecosystem: "npm" 10 | directory: "/" 11 | schedule: 12 | interval: "weekly" 13 | -------------------------------------------------------------------------------- /.github/workflows/buildTest.yml: -------------------------------------------------------------------------------- 1 | name: testBuilding 2 | 3 | on: [push] 4 | jobs: 5 | build: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v3 9 | - uses: actions/setup-node@v3 10 | with: 11 | node-version: 22 12 | - run: npm install 13 | - run: npm run build 14 | - run: npm run build:release 15 | - run: npm run testserve 16 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # ******** NOTE ******** 12 | 13 | name: "CodeQL" 14 | 15 | on: 16 | push: 17 | branches: [ main ] 18 | pull_request: 19 | # The branches below must be a subset of the branches above 20 | branches: [ main ] 21 | schedule: 22 | - cron: '39 13 * * 1' 23 | 24 | jobs: 25 | analyze: 26 | name: Analyze 27 | runs-on: ubuntu-latest 28 | 29 | strategy: 30 | fail-fast: false 31 | matrix: 32 | language: [ 'javascript' ] 33 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 34 | # Learn more... 35 | # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection 36 | 37 | steps: 38 | - name: Checkout repository 39 | uses: actions/checkout@v2 40 | 41 | # Initializes the CodeQL tools for scanning. 42 | - name: Initialize CodeQL 43 | uses: github/codeql-action/init@v1 44 | with: 45 | languages: ${{ matrix.language }} 46 | # If you wish to specify custom queries, you can do so here or in a config file. 47 | # By default, queries listed here will override any specified in a config file. 48 | # Prefix the list here with "+" to use these queries and those in the config file. 49 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 50 | 51 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 52 | # If this step fails, then you should remove it and run the build manually (see below) 53 | - name: Autobuild 54 | uses: github/codeql-action/autobuild@v1 55 | 56 | # ℹ️ Command-line programs to run using the OS shell. 57 | # 📚 https://git.io/JvXDl 58 | 59 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 60 | # and modify them (or add more) to build your code if your project 61 | # uses a compiled language 62 | 63 | #- run: | 64 | # make bootstrap 65 | # make release 66 | 67 | - name: Perform CodeQL Analysis 68 | uses: github/codeql-action/analyze@v1 69 | -------------------------------------------------------------------------------- /.github/workflows/gpr-name.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | 3 | function changeName (fileName, name) { 4 | const data = JSON.parse(fs.readFileSync(fileName, 'utf8')) 5 | data.name = name 6 | fs.writeFileSync(fileName, JSON.stringify(data, null, 2)) 7 | } 8 | 9 | process.argv.slice(3).forEach(fileName => changeName(fileName, process.argv[2])) 10 | -------------------------------------------------------------------------------- /.github/workflows/npm-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages 3 | 4 | name: Node.js Package 5 | 6 | on: 7 | workflow_dispatch: 8 | release: 9 | types: [published] 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v3 16 | - uses: actions/setup-node@v3 17 | with: 18 | node-version: 22 19 | - run: npm ci 20 | #- run: npm test 21 | - run: npm run build 22 | - run: npm run build:release 23 | 24 | publish-npm: 25 | needs: build 26 | runs-on: ubuntu-latest 27 | steps: 28 | - uses: actions/checkout@v3 29 | - uses: actions/setup-node@v3 30 | with: 31 | node-version: 22 32 | registry-url: https://registry.npmjs.org/ 33 | - run: npm ci 34 | - run: npm publish 35 | env: 36 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 37 | 38 | publish-gpr: 39 | needs: build 40 | runs-on: ubuntu-latest 41 | steps: 42 | - uses: actions/checkout@v3 43 | - uses: actions/setup-node@v3 44 | with: 45 | node-version: 22 46 | registry-url: https://npm.pkg.github.com/ 47 | - run: node $GITHUB_WORKSPACE/.github/workflows/gpr-name.js @$GITHUB_REPOSITORY package.json package-lock.json 48 | - run: npm ci 49 | - run: npm publish --registry https://npm.pkg.github.com/ 50 | env: 51 | NODE_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | 84 | # Gatsby files 85 | .cache/ 86 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 87 | # https://nextjs.org/blog/next-9-1#public-directory-support 88 | # public 89 | 90 | # vuepress build output 91 | .vuepress/dist 92 | 93 | # Serverless directories 94 | .serverless/ 95 | 96 | # FuseBox cache 97 | .fusebox/ 98 | 99 | # DynamoDB Local files 100 | .dynamodb/ 101 | 102 | # TernJS port file 103 | .tern-port 104 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 cvzi 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 | # rollup-userscript-template 2 | 3 | This is a template repository for a userscript. 4 | It bundles typescript, react and JSX/TSX script files into a single userscript file with [rollup](https://rollupjs.org/) 5 | 6 | ## Features: 7 | * Bundle everything into a single userscript files in [dist/bundle.user.js](dist/bundle.user.js) 8 | * Typescript support 9 | * React support 10 | * JSX/TSX support 11 | * Metablock generation from [package.json](package.json) and [meta.json](meta.json) with [rollup-plugin-userscript-metablock](https://github.com/FlandreDaisuki/rollup-plugin-userscript-metablock) 12 | * Development script in [dist/dev.user.js](dist/dev.user.js). Automatically fetches the newest version on F5 13 | * Source map support 14 | 15 | ## Installation 16 | 17 | Clone the repository and install dependencies with npm 18 | ```sh 19 | git clone git@github.com:cvzi/rollup-userscript-template.git 20 | cd rollup-userscript-template 21 | npm install 22 | ``` 23 | 24 | ## Usage 25 | 26 | ### Bundle 27 | 28 | Bundle everything from `src/` into `dist/bundle.user.js`: 29 | 30 | `npm run build` 31 | 32 | or 33 | 34 | `npx rollup --config` 35 | 36 | 37 | ### Development server 38 | `npm run serve` 39 | 40 | or 41 | 42 | `node -r esm server.js` 43 | 44 | This will automatically update `dist/bundle.user.js` when code changes and serve it on [localhost:8124](http://localhost:8124/). 45 | 46 | It also creates a second userscript `dist/dev.user.js`, if you install it in Tampermonkey, it will automatically fetch the latest version from http://localhost:8124/bundle.user.js once you reload a website with F5. 47 | 48 | 49 | ### Bundle without source map 50 | 51 | Bundle for publishing without sourcemapping to `dist/release-3.2.1.user.js` 52 | 53 | `npm run build:release` 54 | 55 | or on Windows 56 | 57 | `npm run build:release:win32` 58 | 59 | 60 | ## Other 61 | 62 | * Typescript types for GM.* object are incomplete. See [types/GM/index.d.ts](types/GM/index.d.ts) 63 | * Currently react is not bundled, but imported with @require. To bundle it, remove `output.globals` and `external` from [rollup.config.mjs](rollup.config.mjs) 64 | -------------------------------------------------------------------------------- /dev.user.js: -------------------------------------------------------------------------------- 1 | /* globals GM */ 2 | 3 | 'use strict'; 4 | 5 | (function () { 6 | const url = `http://localhost:%PORT%/bundle.user.js?${Date.now()}` 7 | new Promise(function loadBundleFromServer (resolve, reject) { 8 | const req = GM.xmlHttpRequest({ 9 | method: 'GET', 10 | url: url, 11 | onload: function (r) { 12 | if (r.status !== 200) { 13 | return reject(r) 14 | } 15 | resolve(r.responseText) 16 | }, 17 | onerror: e => reject(e) 18 | }) 19 | if (req && 'catch' in req) { 20 | req.catch(e => { /* ignore */ }) 21 | } 22 | }).catch(function (e) { 23 | const log = function (obj, b) { 24 | let prefix = 'loadBundleFromServer: ' 25 | try { 26 | prefix = GM.info.script.name + ': ' 27 | } catch (e) {} 28 | if (b) { 29 | console.log(prefix + obj, b) 30 | } else { 31 | console.log(prefix, obj) 32 | } 33 | } 34 | if (e && 'status' in e) { 35 | if (e.status <= 0) { 36 | log('Server is not responding') 37 | GM.getValue('scriptlastsource3948218', false).then(function (src) { 38 | if (src) { 39 | log('%cExecuting cached script version', 'color: Crimson; font-size:x-large;') 40 | /* eslint-disable no-eval */ 41 | eval(src) 42 | } 43 | }) 44 | } else { 45 | log('HTTP status: ' + e.status) 46 | } 47 | } else { 48 | log(e) 49 | } 50 | }).then(function (s) { 51 | if (s) { 52 | /* eslint-disable no-eval */ 53 | eval(`${s} 54 | //# sourceURL=${url}`) 55 | GM.setValue('scriptlastsource3948218', s) 56 | } 57 | }) 58 | })() 59 | -------------------------------------------------------------------------------- /dist/bundle.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name rollup-userscript-template 3 | // @description Bundle typescript, react and JSX/TSX script files into a single userscript file with rollup 4 | // @namespace github.com/cvzi 5 | // @require https://unpkg.com/react@18/umd/react.development.js 6 | // @require https://unpkg.com/react-dom@18/umd/react-dom.development.js 7 | // @match https://github.com/* 8 | // @version 1.4.0 9 | // @homepage https://github.com/cvzi/rollup-userscript-template 10 | // @author cuzi 11 | // @license MIT 12 | // @grant GM.getValue 13 | // ==/UserScript== 14 | 15 | 16 | /* 17 | MIT License 18 | 19 | Copyright (c) 2020 cvzi 20 | 21 | Permission is hereby granted, free of charge, to any person obtaining a copy 22 | of this software and associated documentation files (the "Software"), to deal 23 | in the Software without restriction, including without limitation the rights 24 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 25 | copies of the Software, and to permit persons to whom the Software is 26 | furnished to do so, subject to the following conditions: 27 | 28 | The above copyright notice and this permission notice shall be included in all 29 | copies or substantial portions of the Software. 30 | 31 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 32 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 33 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 34 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 35 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 36 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 37 | SOFTWARE. 38 | */ 39 | 40 | /* globals React, ReactDOM */ 41 | (function (ReactDOM, React$1) { 42 | 'use strict'; 43 | 44 | function _interopNamespaceDefault(e) { 45 | var n = Object.create(null); 46 | if (e) { 47 | Object.keys(e).forEach(function (k) { 48 | if (k !== 'default') { 49 | var d = Object.getOwnPropertyDescriptor(e, k); 50 | Object.defineProperty(n, k, d.get ? d : { 51 | enumerable: true, 52 | get: function () { return e[k]; } 53 | }); 54 | } 55 | }); 56 | } 57 | n.default = e; 58 | return Object.freeze(n); 59 | } 60 | 61 | var ReactDOM__namespace = /*#__PURE__*/_interopNamespaceDefault(ReactDOM); 62 | var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React$1); 63 | 64 | var helloWorld = (function () { 65 | window.setTimeout(function delayedError() { 66 | throw 'I am a delayed error'; 67 | }, 3000); 68 | return 'Plain javascript'; 69 | })(); 70 | 71 | var getSomeValueFromGM = (function () { 72 | return () => GM.getValue('test', 'a default value'); 73 | })(); 74 | 75 | class SomeList extends React__namespace.Component { 76 | constructor(props) { 77 | super(props); 78 | } 79 | render() { 80 | return (React__namespace.createElement("div", { className: "some-list" }, 81 | React__namespace.createElement("h1", null, 82 | "This is a list for ", 83 | this.props.name), 84 | React__namespace.createElement("ul", null, 85 | React__namespace.createElement("li", null, "plain javascript"), 86 | React__namespace.createElement("li", null, "typescript"), 87 | React__namespace.createElement("li", null, "react"), 88 | React__namespace.createElement("li", null, "JSX/TSX")))); 89 | } 90 | } 91 | 92 | document.body.innerHTML = ''; 93 | const root = ReactDOM__namespace.createRoot(document.body.appendChild(document.createElement('div'))); 94 | root.render(/*#__PURE__*/React.createElement(SomeList, { 95 | name: helloWorld 96 | })); 97 | getSomeValueFromGM().then(function (s) { 98 | root.render(/*#__PURE__*/React.createElement(SomeList, { 99 | name: s 100 | })); 101 | }); 102 | 103 | })(ReactDOM, React); 104 | //# sourceMappingURL=bundle.user.js.map 105 | -------------------------------------------------------------------------------- /dist/bundle.user.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"bundle.user.js","sources":["../src/examplePlain.js","../src/exampleTypedGM.ts","../src/exampleReact.tsx","../src/index.js"],"sourcesContent":["export default (function () {\n window.setTimeout(function delayedError() {\n throw 'I am a delayed error'\n }, 3000)\n return 'Plain javascript'\n})()\n","export default (function () {\n return () => GM.getValue('test', 'a default value')\n})()\n","import * as React from 'react'\n\ninterface ISomeListProps {\n name?: string;\n}\n\ninterface ISomeListState {\n}\n\nclass SomeList extends React.Component {\n constructor (props: ISomeListProps) {\n super(props)\n }\n render() {\n return (\n
\n

This is a list for {this.props.name}

\n \n
\n )\n }\n}\n\n\nexport default SomeList","import * as ReactDOM from 'react-dom'\n\nimport helloWorld from './examplePlain.js'\nimport getSomeValueFromGM from './exampleTypedGM.ts'\nimport SomeList from './exampleReact.tsx'\n\ndocument.body.innerHTML = ''\n\nconst root = ReactDOM.createRoot(document.body.appendChild(document.createElement('div')))\n\nroot.render(\n \n)\n\ngetSomeValueFromGM().then(function (s) {\n root.render(\n \n )\n})\n"],"names":["window","setTimeout","delayedError","React","document","body","innerHTML","root","ReactDOM","createRoot","appendChild","createElement","render","SomeList","name","helloWorld","getSomeValueFromGM","then","s"],"mappings":"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"} -------------------------------------------------------------------------------- /dist/dev.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name rollup-userscript-template [dev] 3 | // @description Bundle typescript, react and JSX/TSX script files into a single userscript file with rollup 4 | // @namespace github.com/cvzi 5 | // @require https://unpkg.com/react@18/umd/react.development.js 6 | // @require https://unpkg.com/react-dom@18/umd/react-dom.development.js 7 | // @match https://github.com/* 8 | // @version 1.4.0 9 | // @homepage https://github.com/cvzi/rollup-userscript-template 10 | // @author cuzi 11 | // @license MIT 12 | // @grant GM.getValue 13 | // @grant GM.xmlHttpRequest 14 | // @grant GM.setValue 15 | // ==/UserScript== 16 | 17 | /* globals GM */ 18 | 19 | 'use strict'; 20 | 21 | (function () { 22 | const url = `http://localhost:8124/bundle.user.js?${Date.now()}` 23 | new Promise(function loadBundleFromServer (resolve, reject) { 24 | const req = GM.xmlHttpRequest({ 25 | method: 'GET', 26 | url: url, 27 | onload: function (r) { 28 | if (r.status !== 200) { 29 | return reject(r) 30 | } 31 | resolve(r.responseText) 32 | }, 33 | onerror: e => reject(e) 34 | }) 35 | if (req && 'catch' in req) { 36 | req.catch(e => { /* ignore */ }) 37 | } 38 | }).catch(function (e) { 39 | const log = function (obj, b) { 40 | let prefix = 'loadBundleFromServer: ' 41 | try { 42 | prefix = GM.info.script.name + ': ' 43 | } catch (e) {} 44 | if (b) { 45 | console.log(prefix + obj, b) 46 | } else { 47 | console.log(prefix, obj) 48 | } 49 | } 50 | if (e && 'status' in e) { 51 | if (e.status <= 0) { 52 | log('Server is not responding') 53 | GM.getValue('scriptlastsource3948218', false).then(function (src) { 54 | if (src) { 55 | log('%cExecuting cached script version', 'color: Crimson; font-size:x-large;') 56 | /* eslint-disable no-eval */ 57 | eval(src) 58 | } 59 | }) 60 | } else { 61 | log('HTTP status: ' + e.status) 62 | } 63 | } else { 64 | log(e) 65 | } 66 | }).then(function (s) { 67 | if (s) { 68 | /* eslint-disable no-eval */ 69 | eval(`${s} 70 | //# sourceURL=${url}`) 71 | GM.setValue('scriptlastsource3948218', s) 72 | } 73 | }) 74 | })() -------------------------------------------------------------------------------- /dist/release-1.2.4.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name rollup-userscript-template 3 | // @description Bundle typescript, react and JSX/TSX script files into a single userscript file with rollup 4 | // @namespace github.com/cvzi 5 | // @require https://unpkg.com/react@18/umd/react.development.js 6 | // @require https://unpkg.com/react-dom@18/umd/react-dom.development.js 7 | // @match https://github.com/* 8 | // @version 1.2.4 9 | // @homepage https://github.com/cvzi/rollup-userscript-template 10 | // @author cuzi 11 | // @license MIT 12 | // @grant GM.getValue 13 | // ==/UserScript== 14 | 15 | /* 16 | MIT License 17 | 18 | Copyright (c) 2020 cvzi 19 | 20 | Permission is hereby granted, free of charge, to any person obtaining a copy 21 | of this software and associated documentation files (the "Software"), to deal 22 | in the Software without restriction, including without limitation the rights 23 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 24 | copies of the Software, and to permit persons to whom the Software is 25 | furnished to do so, subject to the following conditions: 26 | 27 | The above copyright notice and this permission notice shall be included in all 28 | copies or substantial portions of the Software. 29 | 30 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 31 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 32 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 33 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 34 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 35 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 36 | SOFTWARE. 37 | */ 38 | 39 | /* globals React, ReactDOM */ 40 | (function (ReactDOM, React$1) { 41 | 'use strict'; 42 | 43 | function _interopNamespace(e) { 44 | if (e && e.__esModule) return e; 45 | var n = Object.create(null); 46 | if (e) { 47 | Object.keys(e).forEach(function (k) { 48 | if (k !== 'default') { 49 | var d = Object.getOwnPropertyDescriptor(e, k); 50 | Object.defineProperty(n, k, d.get ? d : { 51 | enumerable: true, 52 | get: function () { return e[k]; } 53 | }); 54 | } 55 | }); 56 | } 57 | n["default"] = e; 58 | return Object.freeze(n); 59 | } 60 | 61 | var ReactDOM__namespace = /*#__PURE__*/_interopNamespace(ReactDOM); 62 | var React__namespace = /*#__PURE__*/_interopNamespace(React$1); 63 | 64 | var helloWorld = (function () { 65 | window.setTimeout(function delayedError() { 66 | throw 'I am a delayed error'; 67 | }, 3000); 68 | return 'Plain javascript'; 69 | })(); 70 | 71 | var getSomeValueFromGM = (function () { 72 | return () => GM.getValue('test', 'a default value'); 73 | })(); 74 | 75 | class SomeList extends React__namespace.Component { 76 | constructor(props) { 77 | super(props); 78 | } 79 | render() { 80 | return (React__namespace.createElement("div", { className: "some-list" }, 81 | React__namespace.createElement("h1", null, 82 | "This is a list for ", 83 | this.props.name), 84 | React__namespace.createElement("ul", null, 85 | React__namespace.createElement("li", null, "plain javascript"), 86 | React__namespace.createElement("li", null, "typescript"), 87 | React__namespace.createElement("li", null, "react"), 88 | React__namespace.createElement("li", null, "JSX/TSX")))); 89 | } 90 | } 91 | 92 | ReactDOM__namespace.render( /*#__PURE__*/React.createElement(SomeList, { 93 | name: helloWorld 94 | }), document.body); 95 | getSomeValueFromGM().then(function (s) { 96 | ReactDOM__namespace.render( /*#__PURE__*/React.createElement(SomeList, { 97 | name: s 98 | }), document.body); 99 | }); 100 | 101 | })(ReactDOM, React); 102 | -------------------------------------------------------------------------------- /dist/release-1.2.5.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name rollup-userscript-template 3 | // @description Bundle typescript, react and JSX/TSX script files into a single userscript file with rollup 4 | // @namespace github.com/cvzi 5 | // @require https://unpkg.com/react@18/umd/react.development.js 6 | // @require https://unpkg.com/react-dom@18/umd/react-dom.development.js 7 | // @match https://github.com/* 8 | // @version 1.2.5 9 | // @homepage https://github.com/cvzi/rollup-userscript-template 10 | // @author cuzi 11 | // @license MIT 12 | // @grant GM.getValue 13 | // ==/UserScript== 14 | 15 | /* 16 | MIT License 17 | 18 | Copyright (c) 2020 cvzi 19 | 20 | Permission is hereby granted, free of charge, to any person obtaining a copy 21 | of this software and associated documentation files (the "Software"), to deal 22 | in the Software without restriction, including without limitation the rights 23 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 24 | copies of the Software, and to permit persons to whom the Software is 25 | furnished to do so, subject to the following conditions: 26 | 27 | The above copyright notice and this permission notice shall be included in all 28 | copies or substantial portions of the Software. 29 | 30 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 31 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 32 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 33 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 34 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 35 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 36 | SOFTWARE. 37 | */ 38 | 39 | /* globals React, ReactDOM */ 40 | (function (ReactDOM, React$1) { 41 | 'use strict'; 42 | 43 | function _interopNamespaceDefault(e) { 44 | var n = Object.create(null); 45 | if (e) { 46 | Object.keys(e).forEach(function (k) { 47 | if (k !== 'default') { 48 | var d = Object.getOwnPropertyDescriptor(e, k); 49 | Object.defineProperty(n, k, d.get ? d : { 50 | enumerable: true, 51 | get: function () { return e[k]; } 52 | }); 53 | } 54 | }); 55 | } 56 | n.default = e; 57 | return Object.freeze(n); 58 | } 59 | 60 | var ReactDOM__namespace = /*#__PURE__*/_interopNamespaceDefault(ReactDOM); 61 | var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React$1); 62 | 63 | var helloWorld = (function () { 64 | window.setTimeout(function delayedError() { 65 | throw 'I am a delayed error'; 66 | }, 3000); 67 | return 'Plain javascript'; 68 | })(); 69 | 70 | var getSomeValueFromGM = (function () { 71 | return () => GM.getValue('test', 'a default value'); 72 | })(); 73 | 74 | class SomeList extends React__namespace.Component { 75 | constructor(props) { 76 | super(props); 77 | } 78 | render() { 79 | return (React__namespace.createElement("div", { className: "some-list" }, 80 | React__namespace.createElement("h1", null, 81 | "This is a list for ", 82 | this.props.name), 83 | React__namespace.createElement("ul", null, 84 | React__namespace.createElement("li", null, "plain javascript"), 85 | React__namespace.createElement("li", null, "typescript"), 86 | React__namespace.createElement("li", null, "react"), 87 | React__namespace.createElement("li", null, "JSX/TSX")))); 88 | } 89 | } 90 | 91 | ReactDOM__namespace.render( /*#__PURE__*/React.createElement(SomeList, { 92 | name: helloWorld 93 | }), document.body); 94 | getSomeValueFromGM().then(function (s) { 95 | ReactDOM__namespace.render( /*#__PURE__*/React.createElement(SomeList, { 96 | name: s 97 | }), document.body); 98 | }); 99 | 100 | })(ReactDOM, React); 101 | -------------------------------------------------------------------------------- /meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "namespace": "github.com/cvzi", 3 | "copyright": "2020, cuzi (https://openuserjs.org/users/cuzi)", 4 | "require": [ 5 | "https://unpkg.com/react@18/umd/react.development.js", 6 | "https://unpkg.com/react-dom@18/umd/react-dom.development.js" 7 | ], 8 | "match": [ 9 | "https://github.com/*" 10 | ], 11 | "grant": [ 12 | "GM.getValue" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rollup-userscript-template", 3 | "version": "1.4.0", 4 | "description": "Bundle typescript, react and JSX/TSX script files into a single userscript file with rollup", 5 | "keywords": [ 6 | "userscript", 7 | "greasemonkey", 8 | "tampermonkey", 9 | "rollup", 10 | "typescript", 11 | "react" 12 | ], 13 | "homepage": "https://github.com/cvzi/rollup-userscript-template", 14 | "funding": { 15 | "url": "https://github.com/cvzi/rollup-userscript-template/funding_links?fragment=1" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "github:cvzi/rollup-userscript-template" 20 | }, 21 | "bugs": { 22 | "url": "https://github.com/cvzi/rollup-userscript-template/issues", 23 | "email": "cuzi@openmail.cc" 24 | }, 25 | "author": "cuzi", 26 | "license": "MIT", 27 | "scripts": { 28 | "test": "echo \"Error: no test specified\" && exit 1", 29 | "build": "npx rollup --config rollup.config.mjs", 30 | "watch": "npx rollup --config rollup.config.mjs --watch", 31 | "serve": "node server.js", 32 | "start": "node server.js", 33 | "testserve": "node server.js --test", 34 | "build:release": "npx rollup --config rollup.config.mjs --sourcemap 0 --file dist/release-$npm_package_version.user.js", 35 | "build:release:win32": "npx rollup --config rollup.config.mjs --sourcemap 0 --file dist\\release-%npm_package_version%.user.js", 36 | "version": "npm run build:release && git add dist/release-$npm_package_version.user.js", 37 | "prepublishOnly": "npm run build:release && git add dist/release-$npm_package_version.user.js" 38 | }, 39 | "devDependencies": { 40 | "@babel/cli": "^7.25.7", 41 | "@babel/core": "^7.25.7", 42 | "@babel/plugin-proposal-class-properties": "^7.18.6", 43 | "@babel/preset-react": "^7.25.7", 44 | "@rollup/plugin-babel": "^6.0.4", 45 | "@rollup/plugin-commonjs": "^28.0.0", 46 | "@rollup/plugin-node-resolve": "^15.3.0", 47 | "@rollup/plugin-replace": "^6.0.1", 48 | "@rollup/plugin-typescript": "^12.1.0", 49 | "@types/react": "^18.3.11", 50 | "colorette": "^2.0.20", 51 | "esm": "^3.2.25", 52 | "react": "^18.3.1", 53 | "react-dom": "^18.3.1", 54 | "rollup": "^4.24.0", 55 | "rollup-plugin-userscript-metablock": "^0.4.2", 56 | "serve": "^14.2.3", 57 | "tslib": "^2.7.0", 58 | "typescript": "^5.6.2" 59 | }, 60 | "config": { 61 | "port": "8124" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /rollup.config.mjs: -------------------------------------------------------------------------------- 1 | import babel from '@rollup/plugin-babel' 2 | import commonjs from '@rollup/plugin-commonjs' 3 | import nodeResolve from '@rollup/plugin-node-resolve' 4 | import replace from '@rollup/plugin-replace' 5 | import typescriptPlugin from '@rollup/plugin-typescript' 6 | import typescript from 'typescript' 7 | import metablock from 'rollup-plugin-userscript-metablock' 8 | 9 | import fs from 'fs' 10 | import pkg from './package.json' with { type: 'json' } 11 | 12 | fs.mkdir('dist/', { recursive: true }, () => null) 13 | 14 | export default { 15 | input: 'src/index.js', 16 | output: { 17 | file: 'dist/bundle.user.js', 18 | format: 'iife', 19 | name: 'rollupUserScript', 20 | banner: () => ('\n/*\n' + fs.readFileSync('./LICENSE', 'utf8') + '*/\n\n/* globals React, ReactDOM */'), 21 | sourcemap: true, 22 | globals: { 23 | react: 'React', 24 | 'react-dom': 'ReactDOM' 25 | } 26 | }, 27 | plugins: [ 28 | replace({ 29 | 'process.env.NODE_ENV': JSON.stringify('production'), 30 | ENVIRONMENT: JSON.stringify('production'), 31 | preventAssignment: true 32 | }), 33 | nodeResolve({ extensions: ['.js', '.ts', '.tsx'] }), 34 | typescriptPlugin({ typescript }), 35 | commonjs({ 36 | include: [ 37 | 'node_modules/**' 38 | ], 39 | exclude: [ 40 | 'node_modules/process-es6/**' 41 | ] 42 | }), 43 | babel({ babelHelpers: 'bundled' }), 44 | metablock({ 45 | file: './meta.json', 46 | override: { 47 | name: pkg.name, 48 | version: pkg.version, 49 | description: pkg.description, 50 | homepage: pkg.homepage, 51 | author: pkg.author, 52 | license: pkg.license 53 | } 54 | }) 55 | ], 56 | external: id => /^react(-dom)?$/.test(id) 57 | } 58 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const { red, green, cyan, bold, italic } = require('colorette') 2 | const { loadConfigFile } = require('rollup/dist/loadConfigFile.js') 3 | const path = require('path') 4 | const fs = require('fs') 5 | const http = require('http') 6 | const handler = require('serve-handler') 7 | const rollup = require('rollup') 8 | const metablock = require('rollup-plugin-userscript-metablock') 9 | const assert = require('assert').strict 10 | const util = require('util') 11 | 12 | const pkg = require('./package.json') 13 | const meta = require('./meta.json') 14 | 15 | const httpGetStatus = util.promisify((url, cb) => http.get(url, (res) => cb(null, res.statusCode))) 16 | 17 | console.log('👀 watch & serve 🤲\n###################\n') 18 | 19 | const port = pkg.config.port 20 | const destDir = 'dist/' 21 | const devScriptInFile = 'dev.user.js' 22 | 23 | const hyperlink = (url, title) => `\u001B]8;;${url}\u0007${title || url}\u001B]8;;\u0007` 24 | 25 | fs.mkdir('dist/', { recursive: true }, () => null) 26 | 27 | // Start web server 28 | const server = http.createServer((request, response) => { 29 | return handler(request, response, { 30 | public: destDir 31 | }) 32 | }) 33 | server.listen(port, () => { 34 | console.log(`Running webserver at ${hyperlink(`http://localhost:${port}`)}`) 35 | }) 36 | 37 | // Create the userscript for development 'dist/dev.user.js' 38 | const devScriptOutFile = path.join(destDir, devScriptInFile) 39 | console.log(cyan(`generate development userscript ${bold('package.json')}, ${bold('meta.json')}, ${bold(devScriptInFile)} → ${bold(devScriptOutFile)}...`)) 40 | const devScriptContent = fs.readFileSync(devScriptInFile, 'utf8').replace(/%PORT%/gm, port.toString()) 41 | const grants = 'grant' in meta ? meta.grant : [] 42 | if (grants.indexOf('GM.xmlHttpRequest') === -1) { 43 | grants.push('GM.xmlHttpRequest') 44 | } 45 | if (grants.indexOf('GM.setValue') === -1) { 46 | grants.push('GM.setValue') 47 | } 48 | if (grants.indexOf('GM.getValue') === -1) { 49 | grants.push('GM.getValue') 50 | } 51 | const override = { 52 | name: pkg.name + ' [dev]', 53 | version: pkg.version, 54 | description: pkg.description, 55 | homepage: pkg.homepage, 56 | author: pkg.author, 57 | license: pkg.license, 58 | grant: grants 59 | } 60 | if ('connect' in meta) { 61 | override.connect = meta.connect 62 | override.connect.push('localhost') 63 | } 64 | metablock({ 65 | file: './meta.json', 66 | override 67 | }).then(devMetablock => { 68 | const result = devMetablock.renderChunk(devScriptContent, null, { sourcemap: false }) 69 | const outContent = typeof result === 'string' ? result : result.code 70 | fs.writeFileSync(devScriptOutFile, outContent) 71 | console.log(green(`created ${bold(devScriptOutFile)}. Please install in Tampermonkey: `) + hyperlink(`http://localhost:${port}/${devScriptInFile}`)) 72 | 73 | let outFiles = [] 74 | loadConfigFile(path.resolve(__dirname, 'rollup.config.mjs')).then( 75 | async ({ options, warnings }) => { 76 | // Start rollup watch 77 | const watcher = rollup.watch(options) 78 | 79 | // Run tests 80 | if (process.argv.indexOf('--test') !== -1) { 81 | console.log(italic('\n###### Test Mode ######\n')) 82 | setTimeout(async function () { 83 | console.log(italic('Running tests...')) 84 | console.log(`Checking http://localhost:${port}/${devScriptInFile}`) 85 | assert.equal(await httpGetStatus(`http://localhost:${port}/${devScriptInFile}`), 200, `http://localhost:${port}/${devScriptInFile}`) 86 | if (outFiles) { 87 | for (let i = 0; i < outFiles.length; i++) { 88 | const urlPath = outFiles[i].replace(/\\/g, '/') 89 | console.log(`Checking http://localhost:${port}/${urlPath}`) 90 | assert.equal(await httpGetStatus(`http://localhost:${port}/${urlPath}`), 200, `http://localhost:${port}/${urlPath}`) 91 | } 92 | } 93 | console.log(italic('Stopping server and watcher after 10 seconds and exiting.')) 94 | watcher.close() 95 | server.close() 96 | process.exit() 97 | }, 10000) 98 | } 99 | 100 | watcher.on('event', event => { 101 | if (event.code === 'BUNDLE_START') { 102 | console.log(cyan(`bundles ${bold(event.input)} → ${bold(event.output.map(fullPath => path.relative(path.resolve(__dirname), fullPath)).join(', '))}...`)) 103 | } else if (event.code === 'BUNDLE_END') { 104 | outFiles = event.output.map(fullPath => path.relative(path.resolve(destDir), fullPath)) 105 | console.log(green(`created ${bold(event.output.map(fullPath => path.relative(path.resolve(__dirname), fullPath)).join(', '))} in ${event.duration}ms`)) 106 | } else if (event.code === 'ERROR') { 107 | console.log(bold(red('⚠ Error'))) 108 | console.log(event.error) 109 | } 110 | if ('result' in event && event.result) { 111 | event.result.close() 112 | } 113 | }) 114 | 115 | // stop watching 116 | watcher.close() 117 | } 118 | ) 119 | }) 120 | -------------------------------------------------------------------------------- /src/empty.ts: -------------------------------------------------------------------------------- 1 | /* 2 | At least one .ts file in ./src/ is required, otherwise the typescript compiler 3 | will throw an error. This empty .ts file prevents the error. 4 | 5 | You may remove this file, if there are other .ts files 6 | If you don't use typescript, you can remove the typescriptPlugin() from rollup.config.mjs 7 | */ -------------------------------------------------------------------------------- /src/examplePlain.js: -------------------------------------------------------------------------------- 1 | export default (function () { 2 | window.setTimeout(function delayedError() { 3 | throw 'I am a delayed error' 4 | }, 3000) 5 | return 'Plain javascript' 6 | })() 7 | -------------------------------------------------------------------------------- /src/exampleReact.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | 3 | interface ISomeListProps { 4 | name?: string; 5 | } 6 | 7 | interface ISomeListState { 8 | } 9 | 10 | class SomeList extends React.Component { 11 | constructor (props: ISomeListProps) { 12 | super(props) 13 | } 14 | render() { 15 | return ( 16 |
17 |

This is a list for {this.props.name}

18 |
    19 |
  • plain javascript
  • 20 |
  • typescript
  • 21 |
  • react
  • 22 |
  • JSX/TSX
  • 23 |
24 |
25 | ) 26 | } 27 | } 28 | 29 | 30 | export default SomeList -------------------------------------------------------------------------------- /src/exampleTypedGM.ts: -------------------------------------------------------------------------------- 1 | export default (function () { 2 | return () => GM.getValue('test', 'a default value') 3 | })() 4 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import * as ReactDOM from 'react-dom' 2 | 3 | import helloWorld from './examplePlain.js' 4 | import getSomeValueFromGM from './exampleTypedGM.ts' 5 | import SomeList from './exampleReact.tsx' 6 | 7 | document.body.innerHTML = '' 8 | 9 | const root = ReactDOM.createRoot(document.body.appendChild(document.createElement('div'))) 10 | 11 | root.render( 12 | 13 | ) 14 | 15 | getSomeValueFromGM().then(function (s) { 16 | root.render( 17 | 18 | ) 19 | }) 20 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noImplicitAny": false, 4 | "noEmitOnError": true, 5 | "removeComments": false, 6 | "target": "ES2020", 7 | "moduleResolution": "node", 8 | "esModuleInterop": true, 9 | "jsx": "react", 10 | "module": "es6", 11 | "typeRoots": [ 12 | "./types", 13 | "./node_modules/@types", 14 | "./typings.d.ts" 15 | ] 16 | }, 17 | "exclude": [ 18 | "node_modules" 19 | ], 20 | "include": [ 21 | "./src/" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /types/GM/index.d.ts: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | Based on https://github.com/vannhi/userscript-typescript-webpack/blob/master/tampermonkey-module.d.ts 4 | */ 5 | 6 | declare namespace GMType { 7 | type RegisterMenuCommandListener = () => void 8 | type MenuCommandId = number 9 | type StorageValue = string | number | boolean 10 | interface NotificationDetails { 11 | text?: string, 12 | title?: string, 13 | image?: string, 14 | highlight?: boolean, 15 | silent?: boolean, 16 | timeout?: number, 17 | ondone?: NotificationOnDone, 18 | onclick?: NotificationOnClick 19 | } 20 | interface NotificationThis extends NotificationDetails { 21 | id: string 22 | } 23 | type NotificationOnClick = (this: NotificationThis) => any 24 | type NotificationOnDone = (this: NotificationThis, clicked: boolean) => any 25 | } 26 | 27 | interface GM { 28 | getValue(key: string, defaultValue: GMType.StorageValue): Promise 29 | setValue(key: string, value: GMType.StorageValue): Promise 30 | 31 | registerMenuCommand(caption: string, commandFunc: GMType.RegisterMenuCommandListener, accessKey?: string): Promise 32 | unregisterMenuCommand(menuCmdId: GMType.MenuCommandId): Promise 33 | 34 | addStyle(css: string): Promise 35 | 36 | notification(details: GMType.NotificationDetails, ondone?: GMType.NotificationOnDone): Promise 37 | notification(text: string, title: string, image?: string, onclick?: GMType.NotificationOnDone): Promise 38 | } 39 | 40 | declare var GM: GM 41 | declare var unsafeWindow: Window 42 | --------------------------------------------------------------------------------