{"id":13447888,"url":"https://github.com/momocow/webpack-userscript","last_synced_at":"2025-04-08T09:09:02.783Z","repository":{"id":29242490,"uuid":"119177501","full_name":"momocow/webpack-userscript","owner":"momocow","description":"A Webpack plugin for userscript projects. 🙈","archived":false,"fork":false,"pushed_at":"2024-12-23T06:49:45.000Z","size":1563,"stargazers_count":206,"open_issues_count":7,"forks_count":20,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-04-01T07:51:27.118Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://cow.moe/webpack-userscript/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/momocow.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"custom":["https://www.buymeacoffee.com/momocow"]}},"created_at":"2018-01-27T15:45:52.000Z","updated_at":"2025-02-17T09:21:39.000Z","dependencies_parsed_at":"2024-06-18T16:55:24.438Z","dependency_job_id":"a8cf0786-1673-4baf-add1-9ec5a38d7bc7","html_url":"https://github.com/momocow/webpack-userscript","commit_stats":{"total_commits":139,"total_committers":9,"mean_commits":"15.444444444444445","dds":"0.48920863309352514","last_synced_commit":"5bca4c8fa68cd296d58e0c7ada967882c9641f6e"},"previous_names":["momocow/webpack-tampermonkey"],"tags_count":26,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/momocow%2Fwebpack-userscript","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/momocow%2Fwebpack-userscript/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/momocow%2Fwebpack-userscript/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/momocow%2Fwebpack-userscript/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/momocow","download_url":"https://codeload.github.com/momocow/webpack-userscript/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247809963,"owners_count":20999816,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-31T05:01:29.572Z","updated_at":"2025-04-08T09:09:02.759Z","avatar_url":"https://github.com/momocow.png","language":"TypeScript","readme":"# webpack-userscript\n\n[![Test Status](https://github.com/momocow/webpack-userscript/actions/workflows/test.yaml/badge.svg?branch=main)](https://github.com/momocow/webpack-userscript/actions/workflows/test.yaml)\n[![Release Status](https://github.com/momocow/webpack-userscript/actions/workflows/release.yaml/badge.svg?branch=main)](https://github.com/momocow/webpack-userscript/actions/workflows/release.yaml)\n[![Coding Style](https://img.shields.io/badge/coding%20style-recommended-orange.svg?style=flat)](https://gitmoji.carloscuesta.me/)\n[![npm](https://img.shields.io/npm/v/webpack-userscript.svg)](https://www.npmjs.com/package/webpack-userscript/v/latest)\n[![Gitmoji](https://img.shields.io/badge/gitmoji-%20😜%20😍-FFDD67.svg?style=flat-square)](https://gitmoji.carloscuesta.me/)\n\nA Webpack plugin for userscript projects 🙈\n\n- [webpack-userscript](#webpack-userscript)\n  - [Overview](#overview)\n    - [Installation](#installation)\n    - [Usage](#usage)\n    - [Options](#options)\n  - [Concepts](#concepts)\n    - [What does it actually do?](#what-does-it-actually-do)\n      - [Prepend headers to userscripts](#prepend-headers-to-userscripts)\n      - [Generate metadata files](#generate-metadata-files)\n      - [Generate proxyscript files](#generate-proxyscript-files)\n    - [Headers pipeline](#headers-pipeline)\n      - [Load headers](#load-headers)\n      - [Rename ambiguous tags](#rename-ambiguous-tags)\n      - [Resolve base URLs](#resolve-base-urls)\n      - [Process SSRIs](#process-ssris)\n      - [Provide default values for tags](#provide-default-values-for-tags)\n      - [Generate proxyscripts](#generate-proxyscripts)\n      - [Interpolate templates inside values](#interpolate-templates-inside-values)\n      - [Validate headers](#validate-headers)\n      - [Render headers](#render-headers)\n  - [Guides](#guides)\n    - [Hot Development](#hot-development)\n    - [Integration with Webpack Dev Server and TamperMonkey](#integration-with-webpack-dev-server-and-tampermonkey)\n    - [I18n headers](#i18n-headers)\n  - [Furthermore](#furthermore)\n\n## Overview\n\n### Installation\n\n```bash\nnpm i webpack-userscript -D\n```\n\n### Usage\n\nImport and configure the plugin in the `webpack.config.js` as the following example.\n\n```js\nconst { UserscriptPlugin } = require('webpack-userscript');\n\nmodule.exports = {\n  plugins: [new UserscriptPlugin(/* optionally provide more options here */)],\n};\n```\n\n### Options\n\nSee [UserscriptOptions](https://cow.moe/webpack-userscript/types/UserscriptOptions.html) for all configurations.\n\n## Concepts\n\n### What does it actually do?\n\n#### Prepend headers to userscripts\n\nThe main purpose of this plugin is to generate userscript headers, and prepend them as a comment block into output entry scripts whose names are conventionally ending with `.user.js`.\n\nThere are several userscript engines on the internet and some of them call userscript headers in different names; but don't worry because they share the same concept and **almost** the same format.\n\nHere are some references to headers definitions of userscript engines:\n\n- [TamperMonkey: userscript headers](https://www.tampermonkey.net/documentation.php#meta)\n- [GreaseMonkey: metadata block](https://wiki.greasespot.net/Metadata_Block)\n- [GreasyFork: meta keys](https://greasyfork.org/en/help/meta-keys)\n- [ViolentMonkey: metadata block](https://violentmonkey.github.io/api/metadata-block/)\n\n#### Generate metadata files\n\nBesides prepending headers to entry scripts, it can optionally generate metadata files which are userscript files without codes; that is, they contain headers only. Metadata files are used to save bandwidth when checking updates. By convention, their names are ending with `.meta.js`.\n\n#### Generate proxyscript files\n\nThe concept of proxyscript is introduced by this plugin, unlike userscripts and metadata files which are commonly known. The name of a proxyscript should end with `.proxy.user.js`. It is mainly designed to work around the caching behavior of userscript engines like TamperMonkey. It was a pain point for userscript developers who set up a development environment with Webpack Dev Server and require fresh reloads to test their scripts.\n\n \u003e See more details in [issue #63](https://github.com/momocow/webpack-userscript/issues/63)\n\nIt is worth mentioning that with ViolentMonkey you might experience a better reload story, according to [a feedback in issue #63](https://github.com/momocow/webpack-userscript/issues/63#issuecomment-1500167848).\n\n### Headers pipeline\n\n#### Load headers\n\nHeaders can be provided directly as an object, a string referencing to a file, or a function returning an object of headers.\n\nThe plugin will also try to load initial headers from the following fields, `name`, `description`, `version`, `author`, `homepage` and `bugs` in `package.json`.\n\nHeaders files are added as a file dependency; therefore, changes to headers files are watched by Webpack during developing in the watch mode.\n\n#### Rename ambiguous tags\n\nThe main purpose is to fix misspelling or wrong letter case.\n\n- `updateUrl` =\u003e `updateURL`\n- `iconUrl` =\u003e `iconURL`\n- `icon64Url` =\u003e `icon64URL`\n- `installUrl` =\u003e `installURL`\n- `supportUrl` =\u003e `supportURL`\n- `downloadUrl` =\u003e `downloadURL`\n- `homepageUrl` =\u003e `homepageURL`\n\n#### Resolve base URLs\n\nBase URLs are resolved for `downloadURL` and `updateURL`.\n\nIf `updateBaseURL` is not provided, `downloadBaseURL` will be used; if metajs is disabled, `updateURL` will point to the file of userjs.\n\n#### Process SSRIs\n\n[Subresource Integrity](https://www.tampermonkey.net/documentation.php#api:Subresource_Integrity) is used to ensure the 3rd-party assets do not get mocked by the man in the middle.\n\nURLs in `@require` and `@resource` tags can have their SSRIs be generated and locked in a SSRI lock file whose name is default to `ssri-lock.json`.\n\nMissing SSRIs will be computed right in the compilation, which indicates that developers have to ensure their 3rd-party assets to be trustable during compilation.\n\nIf one cannot ensure 3rd-party assets to be trustable, he can modify the lock file himself with trustable integrities of assets.\n\n\u003e Note that the lock file should be commited into the version control system just like `package-lock.json`.\n\n#### Provide default values for tags\n\nIf there is no any `@include` or `@match` tag provided, a wildcard `@match`, `*://*/*`, is used.\n\n#### Generate proxyscripts\n\nThe content of a proxyscript looks similar to a metajs except that its `@require` tag will include an URL linked to its userjs file and it won't have any one of these tags, `downloadURL`, `updateURL` and `installURL`.\n\n#### Interpolate templates inside values\n\nLeaf values of headers can be interpolable templates. Template variables can be represented in a format, `[var]`, just like how [template strings in Webpack output options](https://webpack.js.org/configuration/output/#template-strings) look like.\n\nPossible template variables are as follows.\n\n- `[name]`: chunk name\n- `[buildNo]`: build number starting from 1 at beginning of watch mode\n- `[buildTime]`: the timestamp in millisecond when the compilation starts\n- `[file]`: full path of the file\n  - = `[dirname]` + `[basename]` + `[extname]` + `[query]`\n- `[filename]`: file path\n  - = `[basename]` + `[extname]`\n- `[dirname]`: directory path\n- `[basename]`: file base name\n- `[extname]`: file extension starting with `.`\n- `[query]`: query string starting with `?`\n\n\u003e Note that `[buildNo]` starts from 0 and will increase during developing in the watch mode.\n\u003e Once exiting from the watch mode, it will be reset.\n\nFor example, one can embed the build time into `@version` tag via the following configuration.\n\n```js\nnew UserscriptPlugin({\n  headers: {\n    version: '0.0.1-beta.[buildTime]'\n  },\n})\n```\n\n#### Validate headers\n\nHeaders will be transformed and validated with the help of [`class-transformer`](https://github.com/typestack/class-transformer) and [`class-validator`](https://github.com/typestack/class-validator).\n\nThe configuration defaults to strict mode, which means extra tags are not allowed and type checking to headers values are performed.\n\nOne can provide `headersClass` option to override the default `Headers` class; but it is suggested to inherit from the original one.\n\n\u003e Note that the `headersClass` is used for both main headers and i18n headers.\n\u003e Check the [default implementation](lib/features/validate-headers/headers.ts) before customizing your own.\n\n#### Render headers\n\nHeaders in all locales are merged and rendered.\n\nThere are 2 useful options here, `pretty` and `tagOrder`.\n\nThe `pretty` option is a boolean deciding whether to render the headers as a table or not.\n\nThe `tagOrder` option is a precedence list of tag names which should be followed. Listed tags are rendered first; unlisted tags are rendered after listed ones, in ASCII order.\n\n## Guides\n\n### Hot Development\n\nThe following example can be used in development mode with the help of [`webpack-dev-server`](https://github.com/webpack/webpack-dev-server).\n\n`webpack-dev-server` will build the userscript in **watch** mode. Each time the project is built, the `buildNo` variable will increase by 1.\n\n\u003e **Notes**: `buildNo` will be reset to 0 if the dev server is terminated. In this case, if you expect the build version to be persisted during dev server restarting, you can use the `buildTime` variable instead.\n\nIn the following configuration, a portion of the `version` contains the `buildNo`; therefore, each time there is a build, the `version` is also increased so as to indicate a new update available for the script engine like Tampermonkey or GreaseMonkey.\n\nAfter the first time starting the `webpack-dev-server`, you can install the script via `http://localhost:8080/\u003cproject-name\u003e.user.js` (the URL is actually refered to your configuration of `webpack-dev-server`). Once installed, there is no need to manually reinstall the script until you stop the server. To update the script, the script engine has an **update** button on the GUI for you.\n\n- `webpack.config.dev.js`\n\n```js\nconst path = require('path');\nconst { UserscriptPlugin } = require('webpack-userscript');\nconst dev = process.env.NODE_ENV === 'development';\n\nmodule.exports = {\n  mode: dev ? 'development' : 'production',\n  entry: path.resolve(__dirname, 'src', 'index.js'),\n  output: {\n    path: path.resolve(__dirname, 'dist'),\n    filename: '\u003cproject-name\u003e.user.js',\n  },\n  devServer: {\n    contentBase: path.join(__dirname, 'dist'),\n  },\n  plugins: [\n    new UserscriptPlugin({\n      headers(original) {\n        if (dev) {\n          return {\n            ...original,\n            version: `${original.version}-build.[buildNo]`,\n          }\n        }\n\n        return original;\n      },\n    }),\n  ],\n};\n```\n\n### Integration with Webpack Dev Server and TamperMonkey\n\nIf you feel tired with firing the update button on TamperMonkey GUI, maybe you can have a try at proxy script.\n\nA proxy script actually looks similar with the content of `*.meta.js` except that it contains additional `@require` field to include the main userscript. A proxy script is used since TamperMonkey has an option that makes external scripts always be update-to-date without caching, and external scripts are included into userscripts via the `@require` meta field. (You may also want to read this issue, [Tampermonkey/tampermonkey#767](https://github.com/Tampermonkey/tampermonkey/issues/767#issuecomment-542813282))\n\nTo avoid caching and make the main script always be updated after each page refresh, we have to make our main script **\"an external resource\"**. That is where the proxy script comes in, it provides TamperMonkey with a `@require` pointint to the URL of the main script on the dev server, and each time you reload your testing page, it will trigger the update.\n\n\u003e Actually it requires 2 reloads for each change to take effect on the page. The first reload trigger the update of external script but without execution (it runs the legacy version of the script), the second reload will start to run the updated script.\n\u003e\n\u003e I have no idea why TamperMonkey is desinged this way. But..., it's up to you!\n\nTo enable the proxy script, provide a `proxyScript` configuration to the plugin constructor.\n\n`baseURL` should be the base URL of the dev server, and the `filename` is for the proxy script.\n\n\u003e Note: `filename` will be interpolated.\n\nAfter starting the dev server, you can find your proxy script under `\u003cbaseURL\u003e/\u003cfilename\u003e`. In the example below, assume the entry filename is `index.js`, you should visit `http://127.0.0.1:12345/index.proxy.user.js` to install the proxy script on TamperMonkey.\n\nSee [Issue#63](https://github.com/momocow/webpack-userscript/issues/63) for more information.\n\n```js\nnew WebpackUserscript({\n  // \u003c...your other configs...\u003e,\n  proxyScript: {\n    baseURL: 'http://127.0.0.1:12345',\n    filename: '[basename].proxy.user.js',\n  },\n});\n```\n\n\n\n\n### I18n headers\n\nI18n headers can be provided as an object, a string (a.k.a headers file) or a function (a.k.a headers provider), just like the main headers.\n\n```js\nnew UserscriptPlugin({\n  headers: {\n    name: 'this is the main script name'\n  },\n  i18n: {\n    // headers object\n    'en-US': {\n      name: 'this is a localized name'\n    },\n  },\n})\n```\n\n\n```js\nnew UserscriptPlugin({\n  headers: {\n    name: 'this is the main script name'\n  },\n  i18n: {\n    // headers file\n    'en-US': '/dir/to/headers.json' // whose content is `{\"name\": \"this is a localized name\"}`\n  },\n})\n```\n\n```js\nnew UserscriptPlugin({\n  headers: {\n    name: 'this is the main script name'\n  },\n  i18n: {\n    // headers provider\n    'en-US': (headers) =\u003e ({\n      ...headers,\n      name: 'this is a localized name'\n    }),\n  },\n})\n```\n\n\nWith the above configurations will generate the following headers,\n\n```js\n// ==UserScript==\n// @name this is the main script name\n// @name:en-US this is a localized name\n// @version 0.0.0\n// @match *://*/*\n// ==/UserScript==\n```\n\n## Furthermore\n- [Get started with Webpack](https://webpack.js.org/guides/getting-started/)\n- [How to write userscript in TypeScript?](https://github.com/momocow/webpack-userscript/issues/95)\n- [Solution to userscript not refreshing on every page load](https://github.com/momocow/webpack-userscript/issues/63)","funding_links":["https://www.buymeacoffee.com/momocow"],"categories":["JavaScript"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmomocow%2Fwebpack-userscript","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmomocow%2Fwebpack-userscript","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmomocow%2Fwebpack-userscript/lists"}