├── .prettierignore ├── src ├── typings.d.ts ├── style │ └── main.less └── index.ts ├── .gitignore ├── .husky └── pre-commit ├── .github ├── mergify.yml ├── workflows │ ├── ci.yaml │ ├── deploy.yaml │ └── deploy-preview.yaml └── renovate.json ├── config ├── empty.cjs ├── metadata.cjs ├── webpack.config.prod.cjs ├── webpack.config.base.cjs └── webpack.config.dev.cjs ├── tsconfig.json ├── .editorconfig ├── LICENSE ├── package.json ├── readme.cn.md └── readme.md /.prettierignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | -------------------------------------------------------------------------------- /src/typings.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.less"; 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea 3 | .vscode 4 | dist 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npm run lint-staged 5 | -------------------------------------------------------------------------------- /src/style/main.less: -------------------------------------------------------------------------------- 1 | @background_color: yellow; 2 | 3 | body { 4 | background-color: @background_color; 5 | z-index: 20; 6 | } 7 | -------------------------------------------------------------------------------- /.github/mergify.yml: -------------------------------------------------------------------------------- 1 | pull_request_rules: 2 | - name: label conflicts 3 | conditions: 4 | - conflict 5 | actions: 6 | label: 7 | toggle: 8 | - conflict 9 | -------------------------------------------------------------------------------- /config/empty.cjs: -------------------------------------------------------------------------------- 1 | /** 2 | * This is an empty javascript file for webpack to generate a development UserScript without real code. 3 | * So we could make UserScript manager load script file from local file path. 4 | * See webpack.config.dev.js for more details. 5 | */ 6 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist/", 4 | "esModuleInterop": true, 5 | "noImplicitAny": true, 6 | "moduleResolution": "Node", 7 | "module": "ESNext", 8 | "target": "ES2020", 9 | "allowJs": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | charset=utf-8 3 | end_of_line=lf 4 | trim_trailing_whitespace=true 5 | insert_final_newline=true 6 | indent_style=space 7 | indent_size=4 8 | 9 | [{.babelrc,.stylelintrc,.eslintrc,jest.config,*.bowerrc,*.jsb3,*.jsb2,*.json,*.yaml,*.yml}] 10 | indent_style=space 11 | indent_size=2 12 | 13 | [{*.js,*.vue,*.ts,*.cjs,.swcrc}] 14 | indent_style=space 15 | indent_size=2 16 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | push: 5 | branches-ignore: 6 | - gh-pages 7 | - "renovate/**" 8 | pull_request: 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | - name: Setup node 16 | uses: actions/setup-node@v6 17 | with: 18 | node-version: "lts/*" 19 | cache: "npm" 20 | 21 | - run: npm ci 22 | - run: npm run build 23 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "schedule:monthly", 4 | ":dependencyDashboard", 5 | "config:recommended" 6 | ], 7 | "prHourlyLimit": 0, 8 | "lockFileMaintenance": { 9 | "extends": [ 10 | "schedule:weekly" 11 | ], 12 | "automerge": true, 13 | "enabled": true 14 | }, 15 | "automerge": true, 16 | "postUpdateOptions": [ 17 | "npmDedupe" 18 | ], 19 | "separateMajorMinor": false, 20 | "updateNotScheduled": false, 21 | "rangeStrategy": "bump" 22 | } 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2020-2022 Trim21 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import "./style/main.less"; 2 | 3 | //checkout homepage https://github.com/Trim21/gm-fetch for @trim21/gm-fetch 4 | import GM_fetch from "@trim21/gm-fetch"; 5 | 6 | async function main() { 7 | console.log("script start"); 8 | 9 | // cross domain requests 10 | console.log(`uuid: ${await fetchExample()}`); 11 | } 12 | 13 | async function fetchExample(): Promise { 14 | const res = await GM_fetch("https://httpbin.org/uuid"); 15 | const data = await res.json(); 16 | return data.uuid; 17 | } 18 | 19 | main().catch((e) => { 20 | console.log(e); 21 | }); 22 | -------------------------------------------------------------------------------- /config/metadata.cjs: -------------------------------------------------------------------------------- 1 | const { 2 | author, 3 | dependencies, 4 | repository, 5 | version, 6 | } = require("../package.json"); 7 | 8 | module.exports = { 9 | name: { 10 | $: "webpack-userscript-template", 11 | cn: "中文名", 12 | en: "english name", 13 | }, 14 | namespace: "https://trim21.me/", 15 | version: version, 16 | author: author, 17 | source: repository.url, 18 | // 'license': 'MIT', 19 | match: ["*://www.example.com/", "*://example.com/*"], 20 | require: [ 21 | `https://cdn.jsdelivr.net/npm/jquery@${dependencies.jquery}/dist/jquery.min.js`, 22 | ], 23 | grant: ["GM.xmlHttpRequest"], 24 | connect: ["httpbin.org"], 25 | "run-at": "document-end", 26 | }; 27 | -------------------------------------------------------------------------------- /config/webpack.config.prod.cjs: -------------------------------------------------------------------------------- 1 | const { merge } = require("webpack-merge"); 2 | const { 3 | UserScriptMetaDataPlugin, 4 | } = require("userscript-metadata-webpack-plugin"); 5 | 6 | const metadata = require("./metadata.cjs"); 7 | const webpackConfig = require("./webpack.config.base.cjs"); 8 | 9 | const cfg = merge(webpackConfig, { 10 | mode: "production", 11 | output: { 12 | filename: "index.prod.user.js", 13 | }, 14 | optimization: { 15 | // if you need minimize, you need to config minimizer to keep all comments 16 | // to keep userscript meta. 17 | minimize: false, 18 | }, 19 | cache: { 20 | type: "filesystem", 21 | name: "prod", 22 | }, 23 | plugins: [ 24 | new UserScriptMetaDataPlugin({ 25 | metadata, 26 | }), 27 | ], 28 | }); 29 | 30 | module.exports = cfg; 31 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yaml: -------------------------------------------------------------------------------- 1 | # build latest v* tag and upload dist diectory to gh-pages branch. 2 | name: deploy 3 | 4 | on: 5 | push: 6 | branches: 7 | - v* 8 | 9 | jobs: 10 | deploy: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | 15 | - name: Setup node 16 | uses: actions/setup-node@v6 17 | with: 18 | node-version: "lts/*" 19 | cache: "npm" 20 | 21 | - run: npm ci 22 | - run: npm run build 23 | 24 | - name: Deploy 25 | uses: peaceiris/actions-gh-pages@v4 26 | with: 27 | github_token: ${{ secrets.GITHUB_TOKEN }} 28 | publish_dir: ./dist 29 | enable_jekyll: true 30 | commit_message: deploy ${{ github.ref }} 31 | user_name: github-actions[bot] 32 | user_email: github-actions[bot]@users.noreply.github.com 33 | -------------------------------------------------------------------------------- /.github/workflows/deploy-preview.yaml: -------------------------------------------------------------------------------- 1 | # this workflow build from master branch and upload dist directory to "preview-dist" branch 2 | # remove this if you don't need it. 3 | 4 | name: deploy-preview 5 | 6 | on: 7 | push: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | deploy: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | 17 | - name: Setup node 18 | uses: actions/setup-node@v6 19 | with: 20 | node-version: "lts/*" 21 | cache: "npm" 22 | 23 | - run: npm ci 24 | - run: npm run build 25 | 26 | - name: Deploy 27 | uses: peaceiris/actions-gh-pages@v4 28 | with: 29 | github_token: ${{ secrets.GITHUB_TOKEN }} 30 | publish_dir: ./dist 31 | publish_branch: preview-dist 32 | commit_message: deploy ${{ github.ref }} 33 | enable_jekyll: true 34 | user_name: github-actions[bot] 35 | user_email: github-actions[bot]@users.noreply.github.com 36 | -------------------------------------------------------------------------------- /config/webpack.config.base.cjs: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | 3 | const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer"); 4 | 5 | const webpackConfig = { 6 | resolve: { 7 | extensions: [".js", ".ts"], 8 | }, 9 | optimization: { 10 | minimize: false, 11 | moduleIds: "named", 12 | }, 13 | entry: "./src/index.ts", 14 | output: { 15 | path: path.resolve(__dirname, "../dist"), 16 | }, 17 | target: "web", 18 | externals: { 19 | jquery: "$", 20 | }, 21 | module: { 22 | rules: [ 23 | { 24 | test: /\.m?ts$/, 25 | use: { 26 | loader: "ts-loader", 27 | }, 28 | }, 29 | { 30 | test: /\.less$/, 31 | use: ["style-loader", "css-loader", "less-loader"], 32 | }, 33 | { 34 | test: /\.css$/, 35 | use: ["style-loader", "css-loader"], 36 | }, 37 | ], 38 | }, 39 | plugins: process.env.npm_config_report ? [new BundleAnalyzerPlugin()] : [], 40 | }; 41 | 42 | module.exports = webpackConfig; 43 | -------------------------------------------------------------------------------- /config/webpack.config.dev.cjs: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const { merge } = require("webpack-merge"); 3 | const LiveReloadPlugin = require("webpack-livereload-plugin"); 4 | const { 5 | UserScriptMetaDataPlugin, 6 | } = require("userscript-metadata-webpack-plugin"); 7 | 8 | const baseMetadata = require("./metadata.cjs"); 9 | const webpackConfig = require("./webpack.config.base.cjs"); 10 | 11 | const metadata = structuredClone(baseMetadata); 12 | 13 | metadata.require.push( 14 | "file://" + path.resolve(__dirname, "../dist/index.debug.js") 15 | ); 16 | 17 | const cfg = merge(webpackConfig, { 18 | mode: "development", 19 | cache: { 20 | type: "filesystem", 21 | name: "dev", 22 | }, 23 | entry: { 24 | debug: webpackConfig.entry, 25 | "dev.user": path.resolve(__dirname, "./empty.cjs"), 26 | }, 27 | output: { 28 | filename: "index.[name].js", 29 | path: path.resolve(__dirname, "../dist"), 30 | }, 31 | devtool: "eval-source-map", 32 | watch: true, 33 | watchOptions: { 34 | ignored: /node_modules/, 35 | }, 36 | plugins: [ 37 | new LiveReloadPlugin({ 38 | delay: 500, 39 | }), 40 | new UserScriptMetaDataPlugin({ 41 | metadata, 42 | }), 43 | ], 44 | }); 45 | 46 | module.exports = cfg; 47 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webpack-userscript-template", 3 | "description": "Build your UserScript with webpack", 4 | "version": "0.0.1", 5 | "author": { 6 | "name": "Trim21", 7 | "email": "trim21me@gmail.com" 8 | }, 9 | "scripts": { 10 | "format": "prettier -w ./", 11 | "analize": "cross-env npm_config_report=true npm run build", 12 | "build": "webpack --config config/webpack.config.prod.cjs", 13 | "dev": "webpack --config config/webpack.config.dev.cjs", 14 | "prepare": "husky install", 15 | "lint-staged": "lint-staged" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/trim21/webpack-userscript-template" 20 | }, 21 | "private": true, 22 | "dependencies": { 23 | "@trim21/gm-fetch": "^0.3.0", 24 | "jquery": "^3.7.1" 25 | }, 26 | "lint-staged": { 27 | "*.{js,jsx,ts,tsx,json}": [ 28 | "prettier --ignore-path ./.prettierignore --write " 29 | ] 30 | }, 31 | "devDependencies": { 32 | "@types/greasemonkey": "^4.0.7", 33 | "@types/jquery": "^3.5.33", 34 | "@types/node": "^22.17.0", 35 | "browserslist": "^4.25.1", 36 | "cross-env": "^10.1.0", 37 | "css-loader": "^7.1.2", 38 | "husky": "^9.1.7", 39 | "less": "^4.4.2", 40 | "less-loader": "^12.3.0", 41 | "lint-staged": "^16.2.6", 42 | "prettier": "^3.6.2", 43 | "style-loader": "^4.0.0", 44 | "ts-loader": "^9.5.4", 45 | "typescript": "^5.9.2", 46 | "userscript-metadata-webpack-plugin": "^0.4.2", 47 | "webpack": "^5.102.1", 48 | "webpack-bundle-analyzer": "^4.10.2", 49 | "webpack-cli": "^6.0.1", 50 | "webpack-livereload-plugin": "^3.0.2", 51 | "webpack-merge": "^6.0.1", 52 | "webpack-sources": "^3.3.3" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /readme.cn.md: -------------------------------------------------------------------------------- 1 | # 使用 WebPack 来构件 UserScript 2 | 3 | [使用这个 repo 作为模板](https://github.com/Trim21/webpack-userscript-template/generate). 4 | 5 | ## 开发 6 | 7 | 1. 允许 Tampermonkey 访问文件网址 `右键插件图标`-`插件管理页面`-`访问文件网址` 或者参照[官方 faq](https://tampermonkey.net/faq.php?ext=dhdg#Q204) 8 | 2. 使用 `npm ci` or `npm i` 安装依赖。 9 | 3. `npm run dev` 来进行自动编译。 10 | 11 | 在 `dist/` 文件夹会生成两个文件 12 | 13 | - `dist/index.dev.user.js`: **请在浏览器中安装这个油猴脚本** 这个文件不包含有意义的 js 脚本,但是包含了全部的元数据和额外的 `@require file://.../dist/index.debug.js`。 14 | - `dist/index.debug.js`: 这是在 webpack 中启用了 `eval-source-map` 选项之后的打包产物。**不要在浏览器中安装这个脚本**。 15 | 16 | 4. 修改 [src/index.ts](./src/index.ts) 。如果你需要的话你可以引入 css 或者 less 文件。你也可以通过设置 webpack 来引入 scss。 17 | 5. 在 并且打开控制台,你可以看到用户脚本被运行。 18 | 19 | livereload 默认启用。在浏览器中进行自动刷新需要 [这个 chrome 插件](https://chrome.google.com/webstore/detail/jnihajbhpnppcggbcgedagnkighmdlei) 20 | 21 | ### 注意 22 | 23 | 每次你修改了你的[metadata](./config/metadata.cjs),你需要重新安装`index.dev.user.js`。 24 | 25 | ## TypeScript 26 | 27 | 已经设置好了`ts-loader`,可以直接 typescript。[example](src/index.ts) 28 | 29 | ## 跨域请求 30 | 31 | https://github.com/trim21/webpack-userscript-template/blob/master/src/index.ts 32 | 33 | ## 使用依赖 34 | 35 | 有两个办法引入依赖。 36 | 37 | ### 像以往的 UserScript 一样 38 | 39 | 在 [metadata 的 require 部分](./config/metadata.cjs#L13-L17) 中修改你引入的依赖。然后在 [config/webpack.config.base.cjs](./config/webpack.config.base.cjs#L21-L25) 的`exclude`配置中里面把他们排除。 40 | 41 | ### 跟以往的 webpack 一样 42 | 43 | 直接使用 npm 安装,webpack 会自动打包依赖。 44 | 45 | ## build 46 | 47 | ```bash 48 | npm run build 49 | ``` 50 | 51 | `dist/index.prod.user.js` 就是最终打包出来的 UserScript。 52 | 53 | ## 使用其他打包器 54 | 55 | 如果你习惯使用 Vite/rollup/esbuild 等打包器,你也可以直接使用以下这几个包来构建你的用户脚本: 56 | 57 | [gm-fetch](https://github.com/trim21/gm-fetch) 58 | 59 | [userscript-metadata-generator](https://github.com/trim21/userscript-metadata-generator) 60 | 61 | ## deploy 62 | 63 | [github actions](./.github/workflows/nodejs.yml#L68) 会自动在每个 tag 把`dist/index.prod.user.js`部属到`gh-pages`分支的根目录去。 64 | 65 | [example](https://github.com/Trim21/webpack-userscript-template/tree/gh-pages) 66 | 67 | [deployed](https://trim21.github.io/webpack-userscript-template/) 68 | 69 | 也可以使用 greasyfork 的自动同步功能来自动同步此链接。(greasyfork 的代码规则禁止代码混淆或最小化) 70 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # This is a project help you build userscript with webpack 2 | 3 | Just [use this git repo as a template](https://github.com/Trim21/webpack-userscript-template/generate). 4 | 5 | [中文说明](./readme.cn.md) 6 | 7 | ## dev 8 | 9 | 1. Allow Tampermonkey's access to local file URIs [tampermonkey/faq](https://tampermonkey.net/faq.php?ext=dhdg#Q204) 10 | 2. install deps with `npm i` or `npm ci`. 11 | 3. `npm run dev` to start your development. 12 | 13 | Now you will see 2 files in `./dist/` 14 | 15 | - `dist/index.dev.user.js`: **You should install this userscript in your browser.** It's a simple loader that load `dist/index.debug.js` on matched web page. 16 | - `dist/index.debug.js`: This is the development build with `eval-source-map`. It will be automatically loaded by `dist/index.dev.user.js` via `@require file://.../dist/index.debug.js` metadata, **Don't add it to your userscript manager.** 17 | 18 | 4. edit [src/index.ts](./src/index.ts), you can even import css or less files. You can use scss if you like. 19 | 5. go wo and open console, you'll see it's working. 20 | 21 | livereload is default enabled, use [this Chrome extension](https://chrome.google.com/webstore/detail/jnihajbhpnppcggbcgedagnkighmdlei) 22 | 23 | ### NOTICE 24 | 25 | Everytime you change your metadata config, 26 | you'll have to restart webpack server and install newly generated `dist/index.dev.user.js` UserScript in your browser again. 27 | 28 | ## used package 29 | 30 | If you prefer some other bundler like rollup, you can use some of these packages directly. 31 | 32 | [userscript-metadata-generator](https://github.com/trim21/userscript-metadata-generator) 33 | 34 | [gm-fetch](https://github.com/trim21/gm-fetch) 35 | 36 | [userscript-metadata-webpack-plugin](https://github.com/trim21/userscript-metadata-webpack-plugin) 37 | 38 | ## Cross Site Request 39 | 40 | you can call `GM.xmlHttpRequest` directly or use a fetch API based on `GM.xmlHttpRequest` 41 | 42 | ## TypeScript 43 | 44 | use typescript as normal, see [example](src/index.ts) 45 | 46 | ## dependencies 47 | 48 | There are two ways to using a package on npm. 49 | 50 | ### UserScript way 51 | 52 | like original UserScript way, you will need to add them to your [user script metadata's require section](./config/metadata.cjs#L16-L18) , and exclude them in [config/webpack.config.base.cjs](./config/webpack.config.base.cjs#L18-L20) 53 | 54 | ### Webpack way 55 | 56 | just install packages with npm and import them in your code, webpack will take care them. 57 | 58 | ## Build 59 | 60 | ```bash 61 | npm run build 62 | ``` 63 | 64 | `dist/index.prod.user.js` is the final script. you can manually copy it to greasyfork for deploy. 65 | 66 | ### Minify 67 | 68 | There is a [limitation in greasyfork](https://greasyfork.org/en/help/code-rules), your code must not be obfuscated or minified. 69 | 70 | If you don't need to deploy your script to greasyfork, enable minify as you like. 71 | 72 | ## automatically Deploy 73 | 74 | [github actions](./.github/workflows/deploy.yaml#L36) will deploy production userscript to gh-pages branch. 75 | 76 | [example](https://github.com/Trim21/webpack-userscript-template/tree/gh-pages) 77 | 78 | [deployed](https://trim21.github.io/webpack-userscript-template/index.prod.user.js) 79 | 80 | You can auto use greasyfork's auto update function. 81 | 82 | ## Q&A 83 | 84 | you may find enabling source map not working well in production code, because Tampermonkey will add extra lines (all your `@require`) before your script. I don't know if there is a good fix for this, You need to use webpack config `devtool` with `eval` prefix to make it work as expected, so source map is disabled in this production build. 85 | 86 | 87 | --------------------------------------------------------------------------------