chore: merge branch from AnYiEE/AwesomeGadgets

这个提交包含在:
安忆 2024-02-20 13:37:38 +08:00
当前提交 cb7eddae99
签署人:: AnYi
GPG 密钥 ID: 190DF37D01FFE4BC
共有 16 个文件被更改,包括 1260 次插入156 次删除

查看文件

@ -52,6 +52,22 @@
],
"react/no-unknown-property": "off"
}
},
{
"files": ["*.vue"],
"extends": [
"plugin:@typescript-eslint/disable-type-checked",
"plugin:vue/vue3-strongly-recommended",
"@vue/eslint-config-typescript",
"@vue/eslint-config-prettier"
],
"parser": "vue-eslint-parser",
"parserOptions": {
"parser": "@typescript-eslint/parser"
},
"rules": {
"vue/multi-word-component-names": "off"
}
}
],
"rules": {

查看文件

@ -1,7 +1,7 @@
{
"*.{css,less}": ["prettier --write", "stylelint --allow-empty-input --fix"],
"*.{cjs,mjs,js,jsx,ts,tsx}": ["prettier --write", "eslint --fix"],
"*.{cjs,mjs,js,jsx,ts,tsx,vue}": ["prettier --write", "eslint --fix"],
"*.json": "tsx --no-deprecation scripts/run.ts --format-json - ",
"*.svg": "svgo --config=svgo.config.cjs --quiet --input",
"!*.{css,less,cjs,mjs,js,jsx,ts,tsx,json,svg}": "prettier --ignore-unknown --write"
"!*.{css,less,cjs,mjs,js,jsx,ts,tsx,vue,json,svg}": "prettier --ignore-unknown --write"
}

查看文件

@ -13,6 +13,8 @@
"simonsiefke.svg-preview",
"stylelint.vscode-stylelint",
"usernamehw.errorlens",
"vue.volar",
"vue.vscode-typescript-vue-plugin",
"webben.browserslist",
"yoavbls.pretty-ts-errors"
]

查看文件

@ -47,6 +47,9 @@
"[tsx]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[vue]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},

查看文件

@ -14,8 +14,9 @@
Awesome Gadgets 是面向 MediaWiki 网站,用以统一存储、管理并编译全站小工具Gadget的工具。小工具开发者只需关心小工具本身的代码实现,无需关心其他方面。工具将自动检查语法、编译并部署到网站。<br>_Awesome Gadgets_ is a tool designed specifically for MediaWiki websites, with the goal of centralizing the storage, management, and compilation of all site-wide CSS/JavaScript and their peer pages. Developers of these gadgets can solely focus on implementing the code for their creations without the need to worry about other aspects. The tool will automatically check the syntax, compile, and deploy gadgets to the website.
- 使用 esbuild 编译,通过 Babel 转译现代语法以获得良好的浏览器兼容性<br>Compile using esbuild and transpile modern syntax with Babel for good browser compatibility
- 可以编写 TypeScript 和 Less,支持 CSS 和 Less 模块,可以使用 JSX 和 TSX 组件<br>Support writing files in TypeScript and Less, support CSS/Less modules and use JSX/TSX components
- 可以编写 TypeScript 和 Less,支持 CSS 和 Less 模块,可以使用 [jsx-dom](https://www.npmjs.com/package/jsx-dom),可以使用 Vue 组件<br>Support writing files in TypeScript and Less, support CSS/Less modules, [jsx-dom](https://www.npmjs.com/package/jsx-dom) components and Vue components
- 见[文档](docs/how-to-use-jsx-and-tsx-with-jsxdom.md)。<br>See [documentation](docs/how-to-use-jsx-and-tsx-with-jsxdom.md)
- 见[文档](docs/how-to-use-vue.md)。<br>See [documentation](docs/how-to-use-vue.md)
- 支持引用 ResourceLoader 模块<br>Support requiring ResourceLoader built-in modules
- 见[文档](docs/how-to-use-exports-and-require-in-mediawiki.md)。<br>See [documentation](docs/how-to-use-exports-and-require-in-mediawiki.md)
- 支持向多个站点部署并独立控制启用与否<br>Support deploying to multiple sites and independently control whether to enable/disable them

查看文件

@ -14,6 +14,7 @@
2. `*.{tsx, ts}` > `*.{jsx, js}`
3. `*.{jsx, tsx}` > `*.{js, ts}`
4. `*.less` > `*.css`
5. `.vue`仅供被其他脚本导入 / The `.vue` file is only allowed import by other script files
- 如果小工具包含脚本,且使用单独的样式表,则其所使用到的样式表应在脚本中导入<br>If the gadget has script files and style sheets, the style sheets used should be imported in the script file

查看文件

@ -106,4 +106,4 @@ console.log(moment.utc());
```
- `@wikimedia/codex`、`vue`和`pinia`等 Vue 相关的包<br>Vue-related packages such as `@wikimedia/codex`, `vue` and `pinia`
- 可以通过上述方式安装后使用,但仓库本身暂不支持单文件组件(`.vue`),使用范围相对受限<br>They can be used after installation through the above methods, but the repository itself does not currently support single-file components (`.vue`), and their usage is relatively limited.
- 见[文档](how-to-use-vue.md)。<br>See [documentation](how-to-use-vue.md)

查看文件

@ -1,7 +1,7 @@
### 如何使用 jsx-dom?<br>How to use jsx-dom?
[jsx-dom](https://www.npmjs.com/package/jsx-dom) 可以让你使用 JSX/TSX 语法和样式化组件来创建 DOM 元素。它可以被看作是 React 的替代品,比 React 更快,体积也小得非常多。<br>
The package [jsx-dom](https://www.npmjs.com/package/jsx-dom) is a JavaScript library that allows you to use JSX and styled components for creating DOM elements, which is a faster and much smaller alternative to React.
[jsx-dom](https://www.npmjs.com/package/jsx-dom) 可以让你使用 JSX/TSX 语法和样式化组件来创建 DOM 元素。<br>
The package [jsx-dom](https://www.npmjs.com/package/jsx-dom) is a JavaScript library that allows you to use JSX and styled components for creating DOM elements.
在本仓库中使用 jsx-dom,你需要<br>To use `jsx-dom` in this repository, you need to run the following command:
@ -14,7 +14,7 @@ pnpm add jsx-dom
按正常使用 jsx-dom 的方式使用即可。需要注意的是,每个使用 jsx-dom 的小工具都[会打包若干份对应的库](https://github.com/evanw/esbuild/issues/475),可能会影响代码体积。<br>Just use jsx-dom in the normal way. It should be noted that each gadget using them [will bundle several copies of the library](https://github.com/evanw/esbuild/issues/475), which may affect the code size.
```css
/* modules/style.module.css */
/* style.module.css */
.red {
color: #d33;
}
@ -22,10 +22,10 @@ pnpm add jsx-dom
```tsx
import React from 'jsx-dom';
import style from './modules/style.module.css';
import {red} from './style.module.css';
document.body.append(
<div id="id" className={style.red}>
<div id="id" className={red}>
Hello World!
</div>
);

60
docs/how-to-use-vue.md 普通文件
查看文件

@ -0,0 +1,60 @@
### 如何使用 Vue?<br>How to use Vue?
#### 准备依赖 / Prepare dependencies
在本仓库中使用 Vue,有两种方式<br>To use Vue in this repository, there are two ways
- 使用 MediaWiki 内置的`vue`、`pinia`和`@wikimedia/codex`等 Vue 相关的包<br>Use Vue-related packages such as `vue`, `pinia` and `@wikimedia/codex` built into MediaWiki
- 确保目标 MediaWiki 的版本至少为 1.41<br>Ensure that the target MediaWiki version is at least 1.41
- 在小工具对应的`definition.json`中,将 MediaWiki 已经内置的包按需添加为依赖项(`dependencies`<br>In the corresponding `definition.json` of the gadget, add the built-in packages of MediaWiki as dependencies as needed
```jsonc
{
"dependencies": ["vue", "@wikimedia/codex"],
// Other properties...
}
```
- 使用 npm 包<br>Use packages from npm
- 目标 MediaWiki 版本过低,尚未包含 Vue 相关的包,但依然需要使用 Vue<br>The target MediaWiki version is too low and does not yet built-in the Vue-related packages, but still needs to use Vue
- 安装所需的包,直接使用即可<br>Install the required packages directly
#### 示例 / Example
```vue
<!-- App.vue -->
<template lang="pug">
div
p Hello {{msg}}!
</template>
<script setup lang="ts">
defineProps<{
msg: string;
}>();
</script>
<style scoped lang="less">
div {
background-color: #fff;
p {
color: #000;
}
}
</style>
```
```ts
import App from './App.vue';
import {createApp} from 'vue';
const root: HTMLElement = document.createElement('div');
document.body.append(root);
createMwApp(App).mount(root);
```
#### 特别说明 / Notes
- `vue`和`pinia`已经内置在仓库中,无需额外安装<br>`vue` and `pinia` are already built into the repository, no need to install
- `@wikimedia/codex`等其他 Vue 相关的组件包需要额外按需安装<br>Other Vue-related components need to be installed separately
- Vue 组件中可以使用 Less 样式语法和 Pug 模板语法<br>Vue components can use Less style syntax and Pug template syntax

查看文件

@ -1,6 +1,6 @@
{
"name": "awesome-gadgets",
"version": "4.0.1",
"version": "4.2.2",
"description": "Storage, management, compilation, and automatic deployment of MediaWiki gadgets.",
"private": true,
"type": "module",
@ -11,7 +11,7 @@
"deploy:only": "tsx --no-deprecation scripts/run.ts --deploy",
"deploy:test": "tsx --no-deprecation scripts/run.ts --deploy --test",
"sort": "tsx --no-deprecation scripts/run.ts --format-json",
"format": "prettier --cache --write . && pnpm lint:fix && tsc && pnpm minify-svg",
"format": "prettier --cache --write . && pnpm lint:fix && vue-tsc && pnpm minify-svg",
"eslint": "eslint --cache .",
"eslint:fix": "eslint --cache --fix .",
"stylelint": "stylelint --allow-empty-input --cache src/**/*.{css,less}",
@ -73,8 +73,11 @@
"@types/oojs": "^7.0.6",
"@types/oojs-ui": "^0.49.0",
"@types/prompts": "^2.4.9",
"@typescript-eslint/eslint-plugin": "^7.0.1",
"@typescript-eslint/parser": "^7.0.1",
"@typescript-eslint/eslint-plugin": "^7.0.2",
"@typescript-eslint/parser": "^7.0.2",
"@vue/eslint-config-prettier": "^9.0.0",
"@vue/eslint-config-typescript": "^12.0.0",
"@vue/language-plugin-pug": "^1.8.27",
"alpha-sort": "^5.0.0",
"blob-polyfill": "^7.0.20220408",
"broadcastchannel-polyfill": "^1.0.1",
@ -85,12 +88,14 @@
"esbuild": "^0.20.1",
"esbuild-css-modules-plugin": "^3.1.0",
"esbuild-plugin-less": "^1.3.2",
"esbuild-plugin-vue3": "^0.4.2",
"esbuild-postcss": "^0.0.4",
"eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0",
"eslint-config-wikimedia": "latest",
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-unicorn": "latest",
"eslint-plugin-vue": "^9.21.1",
"glob": "^10.3.10",
"happy-dom": "^13.3.8",
"husky": "^9.0.11",
@ -102,12 +107,15 @@
"mwn": "^2.0.2",
"only-allow": "^1.2.1",
"p-queue": "^8.0.1",
"pinia": "^2.1.7",
"postcss": "^8.4.35",
"postcss-import": "^16.0.1",
"postcss-preset-env": "^9.3.0",
"postcss-load-config": "^5.0.3",
"postcss-preset-env": "^9.4.0",
"prettier": "^3.2.5",
"prompts": "^2.4.2",
"proxy-polyfill": "^0.3.2",
"pug": "^3.0.2",
"rimraf": "^5.0.5",
"stylelint": "^15.11.0",
"stylelint-config-wikimedia": "latest",
@ -116,11 +124,14 @@
"types-mediawiki-renovate": "latest",
"typescript": "^5.3.3",
"typescript-plugin-css-modules": "^5.1.0",
"unorm": "^1.6.0"
"unorm": "^1.6.0",
"vue": "^3.4.19",
"vue-eslint-parser": "^9.4.2",
"vue-tsc": "^1.8.27"
},
"pnpm": {
"overrides": {
"@typescript-eslint/utils": "^7.0.1",
"@typescript-eslint/utils": "^7.0.2",
"ansi-regex@<6": "^5.0.1",
"arrify@<3": "^2.0.1",
"balanced-match@<3": "^2.0.0",

文件差异内容过多而无法显示 加载差异

查看文件

@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-unsafe-call */
import {type BuildOptions, type Plugin} from 'esbuild';
import {type Targets, browserslistToTargets} from 'lightningcss';
import {type BuildOptions} from 'esbuild';
import CssModulesPlugin from 'esbuild-css-modules-plugin';
// @ts-expect-error TS7016
import LessPluginNpmImport from 'less-plugin-npm-import';
@ -10,6 +10,8 @@ import {SOURCE_MAP} from '../constant';
import browserslist from 'browserslist';
import {lessLoader} from 'esbuild-plugin-less';
import postcss from 'esbuild-postcss';
import postcssLoadConfig from 'postcss-load-config';
import vuePlugin from 'esbuild-plugin-vue3';
/**
* @summary Do not forget to declare these file extensions in `src/global.d.ts`
@ -24,6 +26,10 @@ const loader = {
'.svg': 'text',
} as const satisfies BuildOptions['loader'];
const lessOptions: Less.Options = {
plugins: [new LessPluginPresetEnv() as unknown as Less.Plugin, new LessPluginNpmImport() as unknown as Less.Plugin],
};
const postcssConfig = await postcssLoadConfig();
const targets: Targets = browserslistToTargets(browserslist());
/**
@ -39,18 +45,18 @@ const esbuildOptions = {
format: 'cjs',
legalComments: 'inline',
plugins: [
vuePlugin({
enableDevTools: false,
postcss: postcssConfig,
preprocessorOptions: lessOptions,
}) as unknown as Plugin,
CssModulesPlugin({
targets,
filter: /\.module\.(?:css|less)$/i,
namedExports: true,
}),
postcss(),
lessLoader({
plugins: [
new LessPluginPresetEnv() as unknown as Less.Plugin,
new LessPluginNpmImport() as unknown as Less.Plugin,
],
}),
lessLoader(lessOptions),
],
sourcemap: SOURCE_MAP ? 'inline' : false,
treeShaking: true,

查看文件

@ -27,6 +27,18 @@ const build = async (): Promise<void> => {
const licenseText: string | undefined = getLicense(gadgetName, license);
if (script || scripts?.length) {
const hasVue: boolean = !!scripts?.some((fileName: string): boolean => {
return fileName.endsWith('.vue');
});
if (hasVue && !script) {
console.log(
chalk.yellow(
`The ${chalk.bold(gadgetName)} uses Vue single-file components, but the entry file does not exist, skip it.`
)
);
continue;
}
const builtScriptFileNames: string[] = await buildFiles(gadgetName, 'script', {
licenseText,
dependencies: definition.dependencies,
@ -34,9 +46,7 @@ const build = async (): Promise<void> => {
});
const scriptFileNames: string = generateFileNames(gadgetName, builtScriptFileNames);
gadgetFiles += scriptFileNames ? `${scriptFileNames}|` : '';
}
if (style || styles?.length) {
} else if (style || styles?.length) {
const builtStyleFileNames: string[] = await buildFiles(gadgetName, 'style', {
licenseText,
files: generateFileArray(style, styles),

查看文件

@ -425,7 +425,7 @@ const findSourceFile = (): SourceFiles => {
type Gadget = SourceFiles[keyof SourceFiles];
const files: Path[] = globSync(['*/*.{js,jsx,ts,tsx,css,less}', '*/definition.json', '*/LICENSE'], {
const files: Path[] = globSync(['*/*.{js,jsx,ts,tsx,vue,css,less}', '*/definition.json', '*/LICENSE'], {
cwd: join(__rootDir, 'src'),
withFileTypes: true,
});
@ -452,7 +452,18 @@ const findSourceFile = (): SourceFiles => {
sourceFiles[gadgetName] ??= {} as Gadget;
const targetGadget = sourceFiles[gadgetName] as Gadget;
const fileExt: string = extname(fileName);
const isScriptFile: boolean = ['.js', '.jsx', '.ts', '.tsx', '.vue'].includes(fileExt);
const isStyleFile: boolean = ['.css', '.less'].includes(fileExt);
const {script, style} = targetGadget;
if (isScriptFile && style) {
delete targetGadget.style;
}
if (isStyleFile && script) {
continue;
}
switch (fileName) {
case 'definition.json':
@ -461,78 +472,68 @@ const findSourceFile = (): SourceFiles => {
case 'index.js':
if (!script || !/^index\.[jt]sx?$/.test(script)) {
targetGadget.script = fileName;
continue;
}
break;
continue;
case 'index.jsx':
if (!script || !/^index\.tsx?$/.test(script)) {
targetGadget.script = fileName;
continue;
}
break;
continue;
case 'index.ts':
if (!script || script !== 'index.tsx') {
targetGadget.script = fileName;
continue;
}
break;
continue;
case 'index.tsx':
targetGadget.script = fileName;
continue;
case 'index.vue':
break;
case `${gadgetName}.js`:
if (!script) {
targetGadget.script = fileName;
continue;
}
break;
continue;
case `${gadgetName}.jsx`:
if (!script || (!/\.tsx?$/.test(script) && script !== 'index.js')) {
targetGadget.script = fileName;
continue;
}
break;
continue;
case `${gadgetName}.ts`:
if (!script || (!/^index\.[jt]sx?$/.test(script) && script !== `${gadgetName}.tsx`)) {
targetGadget.script = fileName;
continue;
}
break;
continue;
case `${gadgetName}.tsx`:
if (!script || !/^index\.[jt]sx?$/.test(script)) {
targetGadget.script = fileName;
continue;
}
continue;
case `${gadgetName}.vue`:
break;
case 'index.css':
if (!script && (!style || style !== 'index.less')) {
if (!style || style !== 'index.less') {
targetGadget.style = fileName;
continue;
}
break;
continue;
case 'index.less':
if (!script) {
targetGadget.style = fileName;
continue;
}
break;
targetGadget.style = fileName;
continue;
case `${gadgetName}.css`:
if (!script && !style) {
if (!style) {
targetGadget.style = fileName;
continue;
}
break;
continue;
case `${gadgetName}.less`:
if (!script && (!style || !/^index\.(?:css|less)/.test(style))) {
if (!style || !/^index\.(?:css|less)/.test(style)) {
targetGadget.style = fileName;
continue;
}
break;
continue;
case 'LICENSE':
targetGadget.license = fileName;
continue;
}
const fileExt: string = extname(fileName);
const removeFiles = (currentFiles: string[], ext: string): string[] => {
return [
...new Set(
@ -543,7 +544,7 @@ const findSourceFile = (): SourceFiles => {
];
};
if (/^\.[jt]sx?$/.test(fileExt)) {
if (isScriptFile) {
targetGadget.scripts ??= [];
const {scripts} = targetGadget;
scripts.push(fileName);
@ -558,7 +559,7 @@ const findSourceFile = (): SourceFiles => {
}
}
if (!script && ['.css', '.less'].includes(fileExt)) {
if (isStyleFile) {
targetGadget.styles ??= [];
const {styles} = targetGadget;
styles.push(fileName);

8
src/global.d.ts vendored
查看文件

@ -7,7 +7,6 @@ declare module '*.module.css' {
const classes: {[key: string]: string};
export default classes;
}
declare module '*.module.less' {
const classes: {[key: string]: string};
export default classes;
@ -23,3 +22,10 @@ declare module '*.jpg';
declare module '*.jpeg';
declare module '*.png';
declare module '*.svg';
declare module '*.vue' {
import type {DefineComponent} from 'vue';
// eslint-disable-next-line @typescript-eslint/ban-types, @typescript-eslint/no-explicit-any
const component: DefineComponent<{}, {}, any>;
export default component;
}

查看文件

@ -54,5 +54,9 @@
"skipLibCheck": true
},
"exclude": ["node_modules/types-mediawiki-renovate/scripts"],
"include": ["node_modules/types-mediawiki-renovate", "scripts/**/*", "src/**/*"]
"include": ["node_modules/types-mediawiki-renovate", "scripts/**/*", "src/**/*", "src/**/*.vue"],
"vueCompilerOptions": {
"plugins": ["@vue/language-plugin-pug"]
}
}