├── .env ├── .env.dev ├── .env.test ├── .github └── workflows │ └── dist.yml ├── .gitignore ├── .nvmrc ├── README.md ├── build.js ├── jsconfig.json ├── package-lock.json ├── package.json ├── snapshot └── 1.png ├── src ├── app.vue ├── components │ ├── AriaConfigDialog.vue │ ├── Header.vue │ ├── PluginDrawer.vue │ └── PluginPanel.vue ├── config │ └── index.js ├── icons │ └── ArrowIcon.vue ├── logic │ ├── actual.js │ ├── index.js │ └── mock.js ├── main.js ├── styles │ └── global.scss └── utils │ └── index.js ├── tampermonkey.js ├── webpack.common.js ├── webpack.dev.js ├── webpack.prod.js └── webpack.test.js /.env: -------------------------------------------------------------------------------- 1 | TAMPERMONKEY_APP_ENVIRONMENT=production 2 | TAMPERMONKEY_APP_NAME=OneDrive 文件下载直链 3 | TAMPERMONKEY_ENTRY_FILE=sharepoint-list-plugin.user.js -------------------------------------------------------------------------------- /.env.dev: -------------------------------------------------------------------------------- 1 | TAMPERMONKEY_APP_ENVIRONMENT=development 2 | TAMPERMONKEY_APP_NAME=OneDrive 文件下载直链-Dev 3 | TAMPERMONKEY_ENTRY_FILE=sharepoint-list-plugin.user.js -------------------------------------------------------------------------------- /.env.test: -------------------------------------------------------------------------------- 1 | TAMPERMONKEY_APP_ENVIRONMENT=production 2 | TAMPERMONKEY_APP_NAME=OneDrive 文件下载直链-Dev 3 | TAMPERMONKEY_ENTRY_FILE=sharepoint-list-plugin.dev.user.js -------------------------------------------------------------------------------- /.github/workflows/dist.yml: -------------------------------------------------------------------------------- 1 | name: Dist 2 | 3 | on: 4 | - push 5 | - pull_request 6 | - workflow_dispatch 7 | 8 | jobs: 9 | build: 10 | name: Build 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | 16 | - name: Setup Node.js environment 17 | uses: actions/setup-node@v2 18 | with: 19 | node-version: 15.x 20 | 21 | - uses: actions/cache@v2 22 | with: 23 | path: ~/.npm 24 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} 25 | restore-keys: | 26 | ${{ runner.os }}-node- 27 | 28 | - name: Install depends 29 | run: npm install 30 | 31 | - name: Build dist 32 | run: npm run build 33 | 34 | - name: Upload dist artifact 35 | uses: actions/upload-artifact@v2 36 | with: 37 | name: dist 38 | path: dist/ 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | 4 | # local env files 5 | .env.local 6 | .env.*.local 7 | 8 | # Log files 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | 13 | # Editor directories and files 14 | .idea 15 | .vscode 16 | 17 | dist 18 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 15 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sharepoint-list-plugin 2 | 3 | [](https://github.com/kaaass/sharepoint-list-plugin/actions/workflows/dist.yml) 4 | 5 | 获得 OneDrive 文件下载直链的 TamperMonkey 插件。推荐使用 Tampermonkey Beta。 6 | 7 |  8 | 9 | ## 通过 Greasy Fork 安装 10 | 11 | https://greasyfork.org/zh-CN/scripts/432415-onedrive-%E6%96%87%E4%BB%B6%E4%B8%8B%E8%BD%BD%E7%9B%B4%E9%93%BE 12 | 13 | ## 构建 14 | 15 | ```shell 16 | npm run build 17 | ``` 18 | 19 | 安装 `dist/sharepoint-list-plugin.user.js` 文件。 20 | 21 | ## Roadmap 22 | 23 | - [x] 支持 Aria2 RPC 24 | - [x] 支持 IDM 批量下载 25 | 26 | ## Credit 27 | 28 | [huangxubo23/tampermonkey-vue 29 | ](https://github.com/huangxubo23/tampermonkey-vue 30 | ) 31 | -------------------------------------------------------------------------------- /build.js: -------------------------------------------------------------------------------- 1 | /** 2 | * TamperMonkey插件打包脚本 3 | */ 4 | 5 | const fs = require('fs') 6 | 7 | const appName = process.env.TAMPERMONKEY_APP_NAME 8 | const appVersion = require('./package.json').version 9 | const entryFile = process.env.TAMPERMONKEY_ENTRY_FILE 10 | 11 | const app = fs.readFileSync(`./dist/${entryFile}`, 'utf8') 12 | let tampermonkeyConfig = fs.readFileSync('./tampermonkey.js', 'utf8') 13 | tampermonkeyConfig = tampermonkeyConfig.replace('__APP_NAME__', appName) 14 | tampermonkeyConfig = tampermonkeyConfig.replace('__APP_VERSION__', appVersion) 15 | fs.writeFileSync(`./dist/${entryFile}`, tampermonkeyConfig + '\n' + app) 16 | console.log('build complete!') 17 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "emitDecoratorMetadata": true, 4 | "experimentalDecorators": true, 5 | "baseUrl": "src", 6 | "paths": { 7 | "@/*": ["*"] 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sharepoint-list-plugin", 3 | "version": "1.2.6", 4 | "description": "获得 OneDrive 文件下载直链的 TamperMonkey 插件", 5 | "author": "KAAAsS", 6 | "private": true, 7 | "main": "/src/main.js", 8 | "keywords": [ 9 | "tampermonkey", 10 | "webpack", 11 | "vue", 12 | "element-ui", 13 | "axios" 14 | ], 15 | "scripts": { 16 | "dev": "dotenv -e .env.dev -- webpack-dev-server --open --config webpack.dev.js", 17 | "build": "dotenv -e .env -- webpack --config webpack.prod.js && dotenv -e .env -- node build.js", 18 | "test": "dotenv -e .env.test -- webpack --config webpack.test.js --watch" 19 | }, 20 | "license": "MIT", 21 | "devDependencies": { 22 | "clean-webpack-plugin": "^3.0.0", 23 | "css-loader": "^5.0.1", 24 | "dotenv-cli": "^4.0.0", 25 | "file-loader": "^6.2.0", 26 | "html-webpack-plugin": "^4.5.0", 27 | "node-sass": "^7.0.1", 28 | "sass-loader": "^12.6.0", 29 | "style-loader": "^2.0.0", 30 | "vue-loader": "^15.9.5", 31 | "vue-template-compiler": "^2.6.12", 32 | "webpack": "^5.71.0", 33 | "webpack-cli": "^3.3.11", 34 | "webpack-dev-server": "^4.7.4", 35 | "webpack-merge": "^5.4.0" 36 | }, 37 | "dependencies": { 38 | "axios": "^0.21.0", 39 | "element-ui": "^2.14.1", 40 | "vue": "^2.6.12" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /snapshot/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaaass/sharepoint-list-plugin/251c956191cd27f538008184c94787aef20e8dfd/snapshot/1.png -------------------------------------------------------------------------------- /src/app.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 24 | 25 | 31 | -------------------------------------------------------------------------------- /src/components/AriaConfigDialog.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 18 | 19 | 20 | 21 | 55 | 56 | -------------------------------------------------------------------------------- /src/components/Header.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ AppName }} 5 | v{{ AppVersion }} 6 | 7 | 8 | 9 | 22 | 23 | -------------------------------------------------------------------------------- /src/components/PluginDrawer.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 42 | 43 | 94 | -------------------------------------------------------------------------------- /src/components/PluginPanel.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | IDM 下载 11 | 12 | Aria 下载 17 | 18 | 复制链接 19 | 20 | 批量下载 21 | 22 | 23 | 29 | 32 | 33 | 36 | 37 | 39 | 40 | 下载 44 | 45 | 复制链接 48 | 49 | 50 | 51 | 52 | 53 | 54 | Cookie 信息 55 | 56 | 57 | 64 | 65 | 66 | 67 | 68 | 75 | 76 | 81 | 82 | 83 | 复制 84 | 85 | 86 | 87 | 88 | 设置 89 | 90 | 配置 Aria RPC 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 210 | 211 | -------------------------------------------------------------------------------- /src/config/index.js: -------------------------------------------------------------------------------- 1 | const AppName = __APP_NAME__ 2 | const AppVersion = __APP_VERSION__ 3 | const AppEnv = __APP_ENVIRONMENT__ 4 | const isDev = AppEnv === 'development' 5 | 6 | export { 7 | AppName, 8 | AppVersion, 9 | AppEnv, 10 | isDev 11 | } -------------------------------------------------------------------------------- /src/icons/ArrowIcon.vue: -------------------------------------------------------------------------------- 1 | 2 | 10 | 15 | 16 | 17 | 18 | 23 | 24 | -------------------------------------------------------------------------------- /src/logic/actual.js: -------------------------------------------------------------------------------- 1 | let cachedUrl = null, 2 | cachedFileList = null; 3 | 4 | /** 5 | * 获得文件列表 6 | * @param allowCache 是否允许缓存 7 | * @returns {Promise<(boolean|*[])[]|boolean[]>} [是否新数据, 数据] 8 | */ 9 | const getFileList = async (allowCache) => { 10 | let fileList = []; 11 | 12 | // 解析路径参数 13 | let url = document.location; 14 | let host = url.host; 15 | let param = new URLSearchParams(url.search); 16 | let loc = param.get('id'); 17 | 18 | // 检查 Cache 19 | if (allowCache) { 20 | if (url.search === cachedUrl) { 21 | console.log("从缓存中解析:", cachedUrl); 22 | return [false, cachedFileList]; 23 | } 24 | } 25 | 26 | // 请求文件列表 27 | let response = await fetch(`https://${host}${loc}`, { 28 | method: 'PROPFIND', 29 | credentials: 'include' 30 | }); 31 | if (!response.ok) { 32 | // 失败 33 | if (response.status !== 404) { 34 | throw new Error(`无法解析OneDrive文件列表: ${response.status}`); 35 | } 36 | // 获取单文件信息 37 | let directLink = encodeURI(`https://${host}${loc}`); 38 | let parts = loc.split('/'); 39 | let filename = parts[parts.length - 1]; 40 | // 单个文件 41 | fileList.push({filename, directLink}); 42 | } else { 43 | // 成功:多个文件 44 | // 解析返回 45 | let xmlRaw = await response.text(); 46 | let parser = new DOMParser(); 47 | let xmlDoc = parser.parseFromString(xmlRaw, "text/xml"); 48 | let fileEls = xmlDoc.getElementsByTagName('D:response'); 49 | for (const el of fileEls) { 50 | let isFolderEl = el.getElementsByTagName('D:isFolder'); 51 | // 当前不支持递归查找,所以跳过文件夹 52 | if (isFolderEl.length >= 1 && isFolderEl[0].textContent === 't') { 53 | continue; 54 | } 55 | // 获得文件 URL 56 | let hrefEl = el.getElementsByTagName('D:href'); 57 | let directLink = ""; 58 | if (hrefEl.length >= 1) { 59 | // Sharepoint 返回中包含 Unicode 字符,需要转义 60 | directLink = encodeURI(hrefEl[0].textContent); 61 | directLink = directLink.replaceAll('%25', '%'); 62 | } 63 | // 获得文件名 64 | let nameEl = el.getElementsByTagName('D:displayname'); 65 | let filename = ""; 66 | if (nameEl.length >= 1) { 67 | filename = nameEl[0].textContent; 68 | } 69 | // 拼接结果 70 | fileList.push({filename, directLink}); 71 | } 72 | } 73 | 74 | // 缓存 75 | cachedUrl = url.search; 76 | cachedFileList = fileList; 77 | return [true, fileList]; 78 | }; 79 | 80 | /** 81 | * 获得 Cookie 信息 82 | * @returns {Promise} 83 | */ 84 | const getCookieNative = (param) => { 85 | return new Promise(function (resolve, reject) { 86 | GM_cookie.list(param, (cookie, error) => { 87 | if (error) { 88 | reject(error); 89 | return; 90 | } 91 | console.log(cookie); 92 | if (cookie.length > 0) { 93 | resolve(cookie[0].value); 94 | } else { 95 | resolve(null); 96 | } 97 | }); 98 | }); 99 | }; 100 | 101 | /** 102 | * 获得 Cookie 103 | * @returns {Promise} 104 | */ 105 | const getCookie = async () => { 106 | let host = document.location.host; 107 | const cookieKeys = [ 108 | {name: 'FedAuth', domain: host}, // 防止多站点之间 Cookie 混淆 109 | {name: 'CCSInfo', domain: '.sharepoint.com'}, // 海外版 110 | {name: 'rtFa', domain: '.sharepoint.com'}, 111 | {name: 'CCSInfo', domain: '.sharepoint.cn'}, // 世纪互联 112 | {name: 'rtFa', domain: '.sharepoint.cn'}, 113 | ]; 114 | let cookies = await Promise.all(cookieKeys.map(getCookieNative)); 115 | let cookie = 'cookie: '; 116 | cookieKeys.forEach((param, idx) => { 117 | let value = cookies[idx]; 118 | if (value) { 119 | cookie += `${param.name}=${value}; `; 120 | } 121 | }); 122 | console.log("获得 Cookie:", cookies); 123 | return cookie; 124 | }; 125 | 126 | /** 127 | * 复制到剪切板 128 | * @returns {Promise} 129 | */ 130 | const setClipboard = (val) => { 131 | return new Promise(function (resolve) { 132 | GM_setClipboard(val, "text"); 133 | resolve(true); 134 | }); 135 | } 136 | 137 | const KEY_ARIA_CONF = "aria_conf"; 138 | 139 | /** 140 | * 取得 Aria 配置 141 | * @returns {{downloadPath: string, apiBase: string, token: string}} 142 | */ 143 | const getAriaConfig = () => { 144 | let json = GM_getValue(KEY_ARIA_CONF, "null"); 145 | return JSON.parse(json); 146 | }; 147 | 148 | /** 149 | * 设置 Aria 配置 150 | * @param conf 151 | */ 152 | const setAriaConfig = (conf) => { 153 | GM_setValue(KEY_ARIA_CONF, JSON.stringify(conf)); 154 | }; 155 | 156 | export default { 157 | getFileList, 158 | getCookie, 159 | setClipboard, 160 | getAriaConfig, 161 | setAriaConfig 162 | } 163 | -------------------------------------------------------------------------------- /src/logic/index.js: -------------------------------------------------------------------------------- 1 | import {isDev} from "@/config"; 2 | import mock from "@/logic/mock.js"; 3 | import actual from "@/logic/actual.js"; 4 | 5 | let selected = actual; 6 | // 开发环境启用 Mock 7 | if (isDev) { 8 | selected = mock; 9 | } 10 | 11 | // 函数 12 | export const getFileList = selected.getFileList; 13 | export const getCookie = selected.getCookie; 14 | export const setClipboard = selected.setClipboard; 15 | 16 | /** 17 | * 构造 IDM 下载列表 18 | * @param fileLists 19 | * @param cookie 20 | * @returns {string} 21 | */ 22 | export const buildEf2File = (fileLists, cookie) => { 23 | return fileLists 24 | .map(link => `<\r\n${link}\r\n${cookie}\r\n>`) 25 | .join("\r\n") + "\r\n\r\n"; 26 | }; 27 | 28 | export const getAriaConfig = selected.getAriaConfig; 29 | export const setAriaConfig = selected.setAriaConfig; 30 | 31 | /** 32 | * 发送 Aria RPC 下载请求 33 | * @param fileLists 34 | * @param cookie 35 | */ 36 | export const sendAriaRequest = async (fileLists, cookie) => { 37 | const conf = getAriaConfig(); 38 | if (!conf) { 39 | return "no_config"; 40 | } 41 | // 构造 RPC Payload 42 | let params = {header: [cookie]}; 43 | if (conf.downloadPath.length > 0) { 44 | params.dir = conf.downloadPath; 45 | } 46 | // 发送 47 | const downloadSingle = async (link) => { 48 | let payload = { 49 | jsonrpc: "2.0", 50 | method: "aria2.addUri", 51 | id: "sharepoint-list-plugin", 52 | params: [ 53 | `token:${conf.token}`, 54 | [link], 55 | params 56 | ] 57 | }; 58 | console.log("发送 Aria RPC:", conf.apiBase, JSON.stringify(payload)); 59 | if (isDev) { 60 | let headers = new Headers(); 61 | headers.append('Content-Type', 'application/json;charset=UTF-8'); 62 | let response = await fetch(conf.apiBase, { 63 | method: 'POST', 64 | body: JSON.stringify(payload), 65 | headers 66 | }); 67 | if (!response.ok) { 68 | throw new Error(response.statusText); 69 | } 70 | return await response.json(); 71 | } else { 72 | // 由于 Mixed-Content,所以只能用 GM_xmlhttpRequest 请求 73 | return await new Promise(function (resolve, reject) { 74 | GM_xmlhttpRequest({ 75 | method: 'POST', 76 | responseType: 'json', 77 | url: conf.apiBase, 78 | data: JSON.stringify(payload), 79 | timeout: 3000, 80 | onload(res) { 81 | if (res.status === 200) { 82 | if (res.response.result) { 83 | // 正常返回 84 | resolve(res.response); 85 | } else { 86 | // 其它错误 87 | reject(`下载错误!${res.response.message}`); 88 | } 89 | } else { 90 | reject(`创建任务失败!${res.responseText}`); 91 | console.error("创建任务失败!", res); 92 | } 93 | }, 94 | ontimeout(res) { 95 | reject("连接超时!"); 96 | console.error("连接超时!", res); 97 | }, 98 | onerror(res) { 99 | reject("无法发送 Aria 下载请求!请尝试添加服务器路径到用户域名白名单。"); 100 | console.error("无法发送 Aria 下载请求!", res); 101 | } 102 | }); 103 | }); 104 | } 105 | }; 106 | return Promise.all(fileLists.map(downloadSingle)); 107 | }; 108 | -------------------------------------------------------------------------------- /src/logic/mock.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 获得文件列表 3 | * @param allowCache 是否允许缓存 4 | * @returns {Promise<(boolean|*[])[]|boolean[]>} [是否新数据, 数据] 5 | */ 6 | const getFileList = async (allowCache) => { 7 | let ret = []; 8 | for (let i = 0; i < 20; i++) { 9 | ret.push({ 10 | filename: '文件名1.rar', 11 | directLink: 'https://aaaaaaaaaaaaaaaaaaaa.bbb/ccccc.ddddd?eeeeeeeeeee' 12 | }) 13 | } 14 | return [true, ret]; 15 | }; 16 | 17 | /** 18 | * 获得 Cookie Mock 19 | * @returns {Promise} 20 | */ 21 | const getCookie = async () => { 22 | return "Cookie: FedAuth="; 23 | }; 24 | 25 | /** 26 | * 复制到剪切板 27 | * @returns {Promise} 28 | */ 29 | const setClipboard = async () => { 30 | return true; 31 | } 32 | 33 | /** 34 | * 取得 Aria 配置 35 | * @returns {{downloadPath: string, apiBase: string, token: string}|null} 36 | */ 37 | const getAriaConfig = () => { 38 | if (!window._aria_config) { 39 | return null; 40 | } 41 | return window._aria_config; 42 | }; 43 | 44 | /** 45 | * 设置 Aria 配置 46 | * @param conf 47 | */ 48 | const setAriaConfig = (conf) => { 49 | console.log('设置新的 Aria 配置:', JSON.stringify(conf)); 50 | window._aria_config = conf; 51 | }; 52 | 53 | export default { 54 | getFileList, 55 | getCookie, 56 | setClipboard, 57 | getAriaConfig, 58 | setAriaConfig 59 | } -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './app.vue' 3 | import {isDev} from './config' 4 | import {loadStyle} from './utils' 5 | import './styles/global.scss' 6 | 7 | loadStyle('https://cdn.staticfile.org/element-ui/2.14.1/theme-chalk/index.css') 8 | 9 | 10 | const id = `app_vue_${Date.now()}` 11 | const root = document.createElement('div') 12 | root.id = id 13 | document.body.appendChild(root) 14 | 15 | if (isDev) { 16 | const ElementUI = require('element-ui') 17 | Vue.use(ElementUI) 18 | } 19 | 20 | new Vue({ 21 | el: `#${id}`, 22 | render: h => h(App) 23 | }) 24 | -------------------------------------------------------------------------------- /src/styles/global.scss: -------------------------------------------------------------------------------- 1 | * { 2 | padding: 0; 3 | margin: 0; 4 | } 5 | 6 | body { 7 | font-size: 14px; 8 | } 9 | 10 | .app { 11 | .el-tag { 12 | height: 20px; 13 | line-height: 20px; 14 | border-radius: 10px; 15 | padding: 0 6px; 16 | } 17 | } 18 | 19 | .app-container { 20 | background-color: #ffffff; 21 | padding: 10px; 22 | } 23 | 24 | -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 加载样式 3 | * @param {String} url 4 | */ 5 | export const loadStyle = (url) => { 6 | const head = document.getElementsByTagName('head')[0]; 7 | const link = document.createElement('link'); 8 | link.rel = 'stylesheet'; 9 | link.type = 'text/css'; 10 | link.href = url; 11 | link.media = 'all'; 12 | head.appendChild(link); 13 | } 14 | 15 | /** 16 | * 触发文本文件下载 17 | * @param content 18 | * @param filename 19 | */ 20 | export const downloadClob = (content, filename) => { 21 | let elDownload = document.createElement('a'); 22 | elDownload.download = filename; 23 | elDownload.style.display = 'none'; 24 | let blob = new Blob([content]); 25 | elDownload.href = URL.createObjectURL(blob); 26 | document.body.appendChild(elDownload); 27 | elDownload.click(); 28 | document.body.removeChild(elDownload); 29 | }; -------------------------------------------------------------------------------- /tampermonkey.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name __APP_NAME__ 3 | // @namespace http://tampermonkey.net/ 4 | // @version __APP_VERSION__ 5 | // @description 获得 OneDrive 文件下载直链的 TamperMonkey 插件 6 | // @author KAAAsS 7 | // @license MIT 8 | // @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw== 9 | // @match https://*.sharepoint.com/* 10 | // @match https://*.sharepoint.cn/* 11 | // @grant GM_cookie 12 | // @grant GM_setClipboard 13 | // @grant GM_setValue 14 | // @grant GM_getValue 15 | // @grant GM_xmlhttpRequest 16 | // @require https://cdn.staticfile.org/vue/2.6.12/vue.min.js 17 | // @require https://cdn.staticfile.org/element-ui/2.14.1/index.min.js 18 | // @connect * 19 | // @noframes 20 | // ==/UserScript== 21 | -------------------------------------------------------------------------------- /webpack.common.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const webpack = require('webpack') 3 | const { CleanWebpackPlugin } = require('clean-webpack-plugin') 4 | const VueLoaderPlugin = require('vue-loader/lib/plugin') 5 | 6 | module.exports = () => { 7 | const entryFile = process.env.TAMPERMONKEY_ENTRY_FILE 8 | return { 9 | entry: { 10 | app: './src/main.js', 11 | }, 12 | output: { 13 | filename: entryFile, 14 | path: path.resolve(__dirname, 'dist') 15 | }, 16 | resolve: { 17 | alias: { 18 | '@': path.resolve(__dirname, 'src') 19 | }, 20 | }, 21 | module: { 22 | rules: [ 23 | { 24 | test: /\.css$/, 25 | use: [ 26 | 'style-loader', 27 | 'css-loader' 28 | ] 29 | }, 30 | { 31 | test: /\.scss$/, 32 | use: [ 33 | 'style-loader', 34 | 'css-loader', 35 | 'sass-loader' 36 | ] 37 | }, 38 | { 39 | test: /\.(png|svg|jpg|gif)$/, 40 | use: [ 41 | 'file-loader' 42 | ] 43 | }, 44 | { 45 | test: /\.vue$/, 46 | loader: 'vue-loader', 47 | options: { 48 | loaders: { 49 | 'scss': [ 50 | 'style-loader', 51 | 'css-loader', 52 | 'sass-loader' 53 | ] 54 | } 55 | } 56 | } 57 | ] 58 | }, 59 | plugins: [ 60 | new VueLoaderPlugin(), 61 | new CleanWebpackPlugin(), 62 | new webpack.DefinePlugin({ 63 | __APP_NAME__: JSON.stringify(process.env.TAMPERMONKEY_APP_NAME), 64 | __APP_ENVIRONMENT__: JSON.stringify(process.env.TAMPERMONKEY_APP_ENVIRONMENT), 65 | __APP_VERSION__: JSON.stringify(require('./package.json').version) 66 | }) 67 | ], 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /webpack.dev.js: -------------------------------------------------------------------------------- 1 | const { merge } = require('webpack-merge') 2 | const common = require('./webpack.common.js') 3 | const webpack = require('webpack') 4 | const path = require('path') 5 | const HtmlWebpackPlugin = require('html-webpack-plugin') 6 | 7 | module.exports = merge(common(), { 8 | mode: 'development', 9 | target: 'web', 10 | devtool: 'inline-source-map', 11 | devServer: { 12 | publicPath: '/', 13 | contentBase: path.join(__dirname, 'dist'), 14 | hot: true // hot reload 15 | }, 16 | plugins: [ 17 | new HtmlWebpackPlugin({ 18 | title: 'test page' 19 | }), 20 | new webpack.HotModuleReplacementPlugin() // hot reload 21 | ] 22 | }) 23 | -------------------------------------------------------------------------------- /webpack.prod.js: -------------------------------------------------------------------------------- 1 | const { merge } = require('webpack-merge') 2 | const common = require('./webpack.common') 3 | 4 | module.exports = merge(common(), { 5 | mode: 'production', 6 | externals: { 7 | // 使用 @require 导入依赖 8 | vue: 'Vue', 9 | 'element-ui': 'element-ui' 10 | }, 11 | // 根据 Greasy Fork 规则取消最小化 12 | optimization: { 13 | minimize: false 14 | }, 15 | }) 16 | -------------------------------------------------------------------------------- /webpack.test.js: -------------------------------------------------------------------------------- 1 | const {merge} = require('webpack-merge') 2 | const common = require('./webpack.common') 3 | 4 | module.exports = merge(common(), { 5 | mode: 'production', 6 | // 根据 Greasy Fork 规则取消最小化 7 | optimization: { 8 | minimize: false 9 | }, 10 | devtool: 'inline-source-map', 11 | }) 12 | --------------------------------------------------------------------------------