├── .gitignore ├── .pnpm-debug.log ├── LICENSE ├── README.md ├── SECURITY.md ├── api ├── bxhApi.js ├── dayAPi.js ├── fsApi.js ├── http.js ├── juejinApi.js └── juejinApiOld.js ├── app.js ├── eApp.js ├── log ├── log-1.json └── log-2.json ├── logs ├── 2022-09-07.log ├── 2022-09-08.log ├── 2022-09-18.log ├── 2022-09-21.log ├── 2022-09-22.log └── 2022-09-23.log ├── package.json ├── pm2.md ├── pnpm-lock.yaml ├── post.js ├── redis ├── app.js ├── redis.md └── 添加数据指令.md ├── task └── index.js └── utils ├── dayApiRobot.js ├── emailSend.js ├── fsRobot.js ├── ioTxt.js ├── node-schedule.js ├── tools.js └── winston.js /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | .env 3 | /logs -------------------------------------------------------------------------------- /.pnpm-debug.log: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Mingo 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 | # automatic-check-script 2 | 3 | ## 掘金自动签到脚本 4 | 5 | * 包括且不限 6 | * - 自动签到、自动每日免费抽奖(签到成功后会送一次)、自动集幸运卡 7 | * - ~~自动收集bug游戏~~ (功能已被官方禁用) 8 | * - ~~vip任务 每日阅读获得珊瑚 (不同等级vip存在不同的每日上限获取数量)【该功能需要开通掘金vip,且提供购买的掘金小册id】~~ (功能已被官方禁用) 9 | * 自动日志记录、执行失败邮件通知 10 | * 设定定时任务node-schedule,目前跑在私人服务器上 11 | * redis支持 ) 12 | * - 如果接入redis 需要在redis目录下创建.env 文件配置环境变量 (数据库连接密码 PASSWORD字段) 13 | * - 如果重新设置过密码 要重启redis服务才能生效 14 | * 支持多人账户管理 15 | 16 | 17 | ## 使用 18 | * pnpm install 19 | * node app.js / nodemon /pm2 都可以 20 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Use this section to tell people about which versions of your project are 6 | currently being supported with security updates. 7 | 8 | | Version | Supported | 9 | | ------- | ------------------ | 10 | | 5.1.x | :white_check_mark: | 11 | | 5.0.x | :x: | 12 | | 4.0.x | :white_check_mark: | 13 | | < 4.0 | :x: | 14 | 15 | ## Reporting a Vulnerability 16 | 17 | Use this section to tell people how to report a vulnerability. 18 | 19 | Tell them where to go, how often they can expect to get an update on a 20 | reported vulnerability, what to expect if the vulnerability is accepted or 21 | declined, etc. 22 | -------------------------------------------------------------------------------- /api/bxhApi.js: -------------------------------------------------------------------------------- 1 | const axios = require("axios"); 2 | const inputHandler = require("../ioTxt"); 3 | axios.interceptors.request.use( 4 | (config) => { 5 | const aheaders = { 6 | Authentication: 7 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsb2dpblRpbWUiOjE2NjE0MjU3MDQyOTcsInVzZXJfaWQiOjQxOTM5OSwidHlwZSI6InVzZXIiLCJtYWluVWlkIjoiNDA1b3BlMjBreTE0N3lnZCIsImlhdCI6MTY2MTQyNTcwNCwiZXhwIjoxNjYyMDMwNTA0fQ.jsDtx2FE96j9YLuDcwUlwRdUsO3CevNGK4Dg-w5tifM", 8 | }; 9 | // @ts-ignore 10 | Object.assign(config.headers, aheaders); 11 | return config; 12 | }, 13 | (error) => { 14 | return error; 15 | } 16 | ); 17 | const getUserInfoApi = (url) => { 18 | axios 19 | .get(url) 20 | .then((response) => { 21 | // console.log(response); 22 | 23 | console.log(response.data); 24 | inputHandler(JSON.stringify(response.data)); 25 | }) 26 | .catch((error) => { 27 | console.log(error); 28 | }); 29 | }; 30 | 31 | const postCollectionsApi = (url) => { 32 | axios 33 | .post(url, { 34 | type: 8, 35 | typeId: 775, 36 | }) 37 | .then((response) => { 38 | // console.log(response); 39 | 40 | console.log(response.data); 41 | inputHandler(JSON.stringify(response.data)); 42 | }) 43 | .catch((error) => { 44 | console.log(error); 45 | }); 46 | }; 47 | 48 | module.exports = { 49 | getUserInfoApi, 50 | postCollectionsApi, 51 | }; 52 | -------------------------------------------------------------------------------- /api/dayAPi.js: -------------------------------------------------------------------------------- 1 | const Http = require("./http"); 2 | // .env文件需和你起服务的文件在同一目录下 3 | const dotenv = require("dotenv"); 4 | dotenv.config(); 5 | const dayToken = 'vVEfUA68tfzDjNgADBMVZ6' 6 | 7 | const initDayHttpAxios = () => { 8 | return new Http(dayToken); 9 | }; 10 | let dayRobotApi = { 11 | sendSignMsg: `https://api.day.app/${dayToken}/sign?url=snssdk2606://juejin.cn/user/center/signin?from=main_page` 12 | } 13 | 14 | 15 | module.exports = { 16 | initDayHttpAxios, 17 | dayRobotApi, 18 | }; 19 | -------------------------------------------------------------------------------- /api/fsApi.js: -------------------------------------------------------------------------------- 1 | const Http = require("./http"); 2 | // .env文件需和你起服务的文件在同一目录下 3 | const dotenv = require("dotenv"); 4 | dotenv.config(); 5 | 6 | const initFsHttpAxios = (token) => { 7 | if (!token) { 8 | token = process.env.fsToken; 9 | } 10 | return new Http(token); 11 | }; 12 | let fsRobotApi = { 13 | sendMsg: 'https://open.feishu.cn/open-apis/bot/v2/hook/d52a1d14-de69-435c-bdbf-c409ba3a1e2c' 14 | } 15 | 16 | 17 | module.exports = { 18 | initFsHttpAxios, 19 | fsRobotApi, 20 | }; 21 | -------------------------------------------------------------------------------- /api/http.js: -------------------------------------------------------------------------------- 1 | const axios = require("axios"); 2 | const emailSend = require("../utils/emailSend"); 3 | const { circularReference } = require("../utils/tools"); 4 | const logger = require("../utils/winston"); 5 | // const inputHandler = require("../ioTxt"); 6 | // axios.defaults.timeout = 10000; 7 | // axios.defaults.headers.post["Content-Type"] = "application/json;charset=UTF-8"; 8 | axios.interceptors.request.use( 9 | (config) => { 10 | const aheaders = { 11 | // "Accept-Language": "zh-CN", 12 | }; 13 | // @ts-ignore 14 | Object.assign(config.headers, aheaders); 15 | return config; 16 | }, 17 | (error) => { 18 | return error; 19 | } 20 | ); 21 | // 响应拦截 22 | axios.interceptors.response.use((config) => { 23 | return config; 24 | }); 25 | 26 | class Http { 27 | token; 28 | constructor(token) { 29 | this.token = token; 30 | axios.defaults.headers.post["cookie"] = token; 31 | axios.defaults.headers.get["cookie"] = token; 32 | } 33 | get(url, params) { 34 | return new Promise((resolve, reject) => { 35 | console.log(url); 36 | axios 37 | .get(url, { params }) 38 | .then((res) => { 39 | // inputHandler(JSON.stringify(res.data)); 40 | logger.info(res.data); 41 | resolve(res.data); 42 | }) 43 | .catch((err) => { 44 | reject(err); 45 | }); 46 | }); 47 | } 48 | 49 | post(url, params) { 50 | return new Promise((resolve, reject) => { 51 | axios 52 | .post(url, params) 53 | .then((res) => { 54 | // inputHandler(JSON.stringify(res.data)); 55 | logger.info(res.data); 56 | // emailSend(JSON.stringify(res.data)); 57 | resolve({ ...res.data, url }); 58 | }) 59 | .catch((err) => { 60 | reject(err.data); 61 | }); 62 | }); 63 | } 64 | } 65 | module.exports = Http; 66 | -------------------------------------------------------------------------------- /api/juejinApi.js: -------------------------------------------------------------------------------- 1 | const Http = require("./http"); 2 | // .env文件需和你起服务的文件在同一目录下 3 | const dotenv = require("dotenv"); 4 | dotenv.config(); 5 | 6 | let h = ""; 7 | const initHttpAxios = (token) => { 8 | // if (!token) { 9 | // token = process.env.token; 10 | // throw new Error('token 不存在') 11 | // } 12 | h = new Http(token); 13 | }; 14 | let bookListUrl = 15 | "https://api.juejin.cn/booklet_api/v1/booklet/bookletshelflist?aid=2608&uuid=7131217957565122084&spider=0"; 16 | // 抽奖 17 | let luckDrawUrl = 18 | "https://api.juejin.cn/growth_api/v1/lottery/draw?aid=2608&uuid=7131217957565122084&spider=0&_signature=_02B4Z6wo00901Ts-dDAAAIDBo1XjQyhwwzU7OnCAAC3KA9zgofc9sQA.WvUK11LdSRyTIZnXUUmmmqyf0Fx9SbwKQxH8b4wJdEdLsjzmtmlaVYIudiTO8mCWKbOD1uGVrw6fdqGjSA.dXyFH92"; 19 | 20 | let loginUrl = 21 | "https://api.juejin.cn/growth_api/v1/check_in?aid=2608&uuid=7131217957565122084&spider=0&_signature=_02B4Z6wo00101TetangAAIDBr8b9CPHTKKE3qW7AAC7v8ihQDke3NjDChM33tV0xnNbB4f9vyrOWcHwQ9mBTrm-.jB8t29Lq-JtRNHy5bnfkcau.iMc0tdXl5hB6lguMl3oml3GyfogXWNTFcd"; 22 | 23 | // 沾沾喜气 24 | let touchHappyUrl = 25 | // "https://api.juejin.cn/growth_api/v1/lottery_lucky/dip_lucky?aid=2608&uuid=7131217957565122084&spider=0"; 26 | "https://api.juejin.cn/growth_api/v1/lottery_lucky/dip_lucky?aid=2608&uuid=7073392340530513442&spider=0"; 27 | // lottery_history_id: "7135665166527512607";add 28 | // 7136452057275465762 29 | 30 | // 沾沾喜气的卡片list 31 | let happyCardUrl = 32 | "https://api.juejin.cn/growth_api/v1/lottery_history/global_big?aid=2608&uuid=7073392340530513442&spider=0"; 33 | let submitReadProgressUrl = 34 | "https://api.juejin.cn/booklet_api/v1/reading/submit_progress?aid=2608&uuid=7131217957565122084&spider=0"; 35 | let getSectionDetailsUrl = 36 | "https://api.juejin.cn/booklet_api/v1/section/get?aid=2608&uuid=7131217957565122084&spider=0"; 37 | 38 | let bookSectionOfViteUrl = 39 | "https://api.juejin.cn/booklet_api/v1/booklet/get?aid=2608&uuid=7131217957565122084&spider=0"; 40 | 41 | // 未收集的bug 42 | let bugfixListUrl = 43 | "https://api.juejin.cn/user_api/v1/bugfix/not_collect?aid=2608&uuid=7073392340530513442&spider=0"; 44 | 45 | let collectBugUrl = 46 | "https://api.juejin.cn/user_api/v1/bugfix/collect?aid=2608&uuid=7073392340530513442&spider=0"; 47 | 48 | let checkTodayStatusUrl = 49 | "https://api.juejin.cn/growth_api/v2/get_today_status?aid=2608&uuid=7131217957565122084&spider=0"; 50 | 51 | const juejinApi = { 52 | getBookList() { 53 | return h.post(bookListUrl); 54 | }, 55 | postSign() { 56 | return h.post(loginUrl); 57 | }, 58 | luckDraw() { 59 | return h.post(luckDrawUrl); 60 | }, 61 | touchHappy(id) { 62 | return h.post(touchHappyUrl, { lottery_history_id: id }); 63 | }, 64 | getHappyCardList() { 65 | return h.post(happyCardUrl, { page_no: 1, page_size: 5 }); 66 | }, 67 | // vip 阅读任务 68 | postReadTask(id) { 69 | // 第一节 7050063812044685343 70 | // 第二节 7077834799208988675; 71 | // 这个接口原本是拿小测某章节的具体内容,但是在后端有隐藏逻辑:每访问一次会执行,vip的每日阅读任务 72 | return h.post(getSectionDetailsUrl, { 73 | section_id: id, 74 | }); 75 | 76 | // return h.post(submitReadProgressUrl, { 77 | // booklet_id: "7050063811973218341", 78 | // reading_position: 17, 79 | // section_id: "7077834799208988675", 80 | // }); 81 | }, 82 | // 获得掘金小册 章节信息 83 | getBookSectionOfVite(id = "7050063811973218341") { 84 | //深入浅出vite 小册 id 7050063811973218341; 85 | return h.post(bookSectionOfViteUrl, { booklet_id: id }); 86 | }, 87 | // 收集bug活动 获取未收集bug列表 88 | getBugListGame() { 89 | return h.post(bugfixListUrl, {}); 90 | // bug_show_type: 1; 91 | // bug_time: 1665158400; 92 | // bug_type: 12; 93 | // is_first: true; 94 | }, 95 | postBugCollectGame({ bug_time, bug_type }) { 96 | return h.post(collectBugUrl, { bug_time, bug_type }); 97 | }, 98 | checkTodayStatus() { 99 | return h.get(checkTodayStatusUrl); 100 | }, 101 | 102 | }; 103 | // 要调用方法 必须先执行初始化方法 initHttpAxios; 104 | module.exports = { 105 | initHttpAxios, 106 | juejinApi, 107 | }; 108 | 109 | // interface juejinApiT { 110 | // getBookList: () => Promise>; 111 | // postSign: () => Promise>; 112 | // luckDraw: () => Promise>; 113 | // touchHappy: (_params: { 114 | // lottery_history_id: string, 115 | // }) => Promise>; 116 | // } 117 | 118 | // interface ResType { 119 | // code: number; 120 | // data?: T; 121 | // msg: string; 122 | // err?: string; 123 | // } 124 | 125 | // interface touchHappyDataT { 126 | // dip_action: number; 127 | // has_dip: boolean; // 今天是否已经领取过 128 | // total_value: number; // 总共幸运值 129 | // dip_value: number; //当前获取的幸运值 130 | // } 131 | -------------------------------------------------------------------------------- /api/juejinApiOld.js: -------------------------------------------------------------------------------- 1 | const axios = require("axios"); 2 | const { circularReference } = require("../utils/tools"); 3 | const inputHandler = require("../ioTxt"); 4 | axios.interceptors.request.use( 5 | (config) => { 6 | const aheaders = { 7 | cookie: 8 | "_ga=GA1.2.817573689.1660366074; _tea_utm_cache_2608=undefined; __tea_cookie_tokens_2608=%257B%2522web_id%2522%253A%25227131217957565122084%2522%252C%2522user_unique_id%2522%253A%25227131217957565122084%2522%252C%2522timestamp%2522%253A1660366074458%257D; passport_csrf_token=8bf1f097d295c60c8cc84d4662713cfb; passport_csrf_token_default=8bf1f097d295c60c8cc84d4662713cfb; MONITOR_WEB_ID=ec040b3a-630d-43f8-804f-c287891371cb; _tea_utm_cache_2018=undefined; _gid=GA1.2.1587286200.1661517736; n_mh=Kt07ecmmto2QqF7hHsSv6O-BST01Y0OAX3z6lbozBqk; sid_guard=a68d38d43bc77d0d5d153b7bcb8438e2%7C1661520811%7C31536000%7CSat%2C+26-Aug-2023+13%3A33%3A31+GMT; uid_tt=7ce151630caeb8c4c06e992f7b83bb1f; uid_tt_ss=7ce151630caeb8c4c06e992f7b83bb1f; sid_tt=a68d38d43bc77d0d5d153b7bcb8438e2; sessionid=a68d38d43bc77d0d5d153b7bcb8438e2; sessionid_ss=a68d38d43bc77d0d5d153b7bcb8438e2; sid_ucp_v1=1.0.0-KDJmMjJlZmM4MTgzOGY3MTFkYjA1NTAzZjJkZWMyNDMxN2M2ZjdmMzAKFwiO4pHVqvTrBRCrl6OYBhiwFDgCQO8HGgJsZiIgYTY4ZDM4ZDQzYmM3N2QwZDVkMTUzYjdiY2I4NDM4ZTI; ssid_ucp_v1=1.0.0-KDJmMjJlZmM4MTgzOGY3MTFkYjA1NTAzZjJkZWMyNDMxN2M2ZjdmMzAKFwiO4pHVqvTrBRCrl6OYBhiwFDgCQO8HGgJsZiIgYTY4ZDM4ZDQzYmM3N2QwZDVkMTUzYjdiY2I4NDM4ZTI", 9 | // "_ga=GA1.2.817573689.1660366074; _tea_utm_cache_2608=undefined; __tea_cookie_tokens_2608=%7B%22web_id%22%3A%227131217957565122084%22%2C%22user_unique_id%22%3A%227131217957565122084%22%2C%22timestamp%22%3A1660366074458%7D; passport_csrf_token=8bf1f097d295c60c8cc84d4662713cfb; passport_csrf_token_default=8bf1f097d295c60c8cc84d4662713cfb; MONITOR_WEB_ID=ec040b3a-630d-43f8-804f-c287891371cb; _gid=GA1.2.937817016.1661427764; _tea_utm_cache_2018=undefined; n_mh=f7IbdOePTc_oRm78V6KzkaK-t_o280ERqKS7el_5R6E; sid_guard=27fbbd7ad05a17df0a35186ea5921ad3|1661429047|31536000|Fri,+25-Aug-2023+12:04:07+GMT; uid_tt=cd5b03ccb424a2b52533dbcad1ee33af; uid_tt_ss=cd5b03ccb424a2b52533dbcad1ee33af; sid_tt=27fbbd7ad05a17df0a35186ea5921ad3; sessionid=27fbbd7ad05a17df0a35186ea5921ad3; sessionid_ss=27fbbd7ad05a17df0a35186ea5921ad3; sid_ucp_v1=1.0.0-KGYwYzc0NzYwNjM2OWU0NGIyODYyYjBkMDIwOTMzMGZjZjYyOWM0ZDQKFwjn3oD9to3pARC3yp2YBhiwFDgCQO8HGgJsZiIgMjdmYmJkN2FkMDVhMTdkZjBhMzUxODZlYTU5MjFhZDM; ssid_ucp_v1=1.0.0-KGYwYzc0NzYwNjM2OWU0NGIyODYyYjBkMDIwOTMzMGZjZjYyOWM0ZDQKFwjn3oD9to3pARC3yp2YBhiwFDgCQO8HGgJsZiIgMjdmYmJkN2FkMDVhMTdkZjBhMzUxODZlYTU5MjFhZDM", 10 | }; 11 | // @ts-ignore 12 | Object.assign(config.headers, aheaders); 13 | return config; 14 | }, 15 | (error) => { 16 | return error; 17 | } 18 | ); 19 | const getBookList = () => { 20 | const url = 21 | "https://api.juejin.cn/booklet_api/v1/booklet/bookletshelflist?aid=2608&uuid=7131217957565122084&spider=0"; 22 | axios 23 | .post(url) 24 | .then((response) => { 25 | // console.log(response); 26 | 27 | console.log(response.data); 28 | // let content = JSON.stringify(response); 29 | 30 | inputHandler(JSON.stringify(response.data)); 31 | }) 32 | .catch((error) => { 33 | console.log(error); 34 | }); 35 | }; 36 | 37 | const postSign = () => { 38 | const url = 39 | "https://api.juejin.cn/growth_api/v1/check_in?aid=2608&uuid=7131217957565122084&spider=0&_signature=_02B4Z6wo00101TetangAAIDBr8b9CPHTKKE3qW7AAC7v8ihQDke3NjDChM33tV0xnNbB4f9vyrOWcHwQ9mBTrm-.jB8t29Lq-JtRNHy5bnfkcau.iMc0tdXl5hB6lguMl3oml3GyfogXWNTFcd"; 40 | 41 | axios 42 | .post(url) 43 | .then((response) => { 44 | // console.log(response); 45 | 46 | console.log(response.data); 47 | let content = circularReference(response); 48 | 49 | inputHandler(JSON.stringify(content)); 50 | // inputHandler(JSON.stringify(response.data)); 51 | }) 52 | .catch((error) => { 53 | console.log(error); 54 | }); 55 | }; 56 | const post = (url) => { 57 | return axios 58 | .post(url) 59 | .then((response) => { 60 | console.log(response.data); 61 | let content = circularReference(response); 62 | inputHandler(JSON.stringify(content)); 63 | // inputHandler(JSON.stringify(response.data)); 64 | }) 65 | .catch((error) => { 66 | console.log(error); 67 | }); 68 | }; 69 | 70 | // 抽奖 71 | let luckDrawUrl = 72 | "https://api.juejin.cn/growth_api/v1/lottery/draw?aid=2608&uuid=7131217957565122084&spider=0&_signature=_02B4Z6wo00901Ts-dDAAAIDBo1XjQyhwwzU7OnCAAC3KA9zgofc9sQA.WvUK11LdSRyTIZnXUUmmmqyf0Fx9SbwKQxH8b4wJdEdLsjzmtmlaVYIudiTO8mCWKbOD1uGVrw6fdqGjSA.dXyFH92"; 73 | 74 | let xiqi = 75 | "https://api.juejin.cn/growth_api/v1/lottery_lucky/dip_lucky?aid=2608&uuid=7131217957565122084&spider=0"; 76 | // lottery_history_id: "7135665166527512607";add 77 | https: module.exports = { 78 | getBookListApi: getBookList, 79 | // getBookListApi(params) { 80 | // return http.get(bookListUrl, params); 81 | // }, 82 | postSignApi: postSign, 83 | // luckDrawApi: post(luckDrawUrl), 84 | }; 85 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const fs = require("fs"); 3 | const app = new express(); 4 | const port = 3000; 5 | const Interval = require("./utils/node-schedule"); 6 | const schedule = require("node-schedule"); 7 | const emailSend = require("./utils/emailSend"); 8 | const redisConnectHandle = require("./redis/app"); 9 | const { initHttpAxios, juejinApi } = require("./api/juejinApi"); 10 | const { getBookList, postSign, luckDraw, getHappyCardList, touchHappy } = 11 | juejinApi; 12 | const { sleep } = require("./utils/tools"); 13 | 14 | const { vipReadTask, bugFixGame, checkSign } = require("./task/index"); 15 | const doTaskHandle = async (isVip) => { 16 | try { 17 | // 签到 18 | // const res = await postSign(); 19 | // if (res.err_no && res.err_no !== 0 && res.err_no !== 15001) { 20 | // throw Error(JSON.stringify(res)); 21 | // } 22 | // 抽奖 23 | // luckDraw(); 24 | const { data } = await getHappyCardList(); 25 | // 抽幸运卡 26 | touchHappy(data.lotteries[0].history_id); 27 | /** 28 | * description: bug 收集游戏 29 | * TODO: 已被官方禁用 30 | */ 31 | // bugFixGame(); 32 | /** 33 | * description: vip任务 34 | * TODO: 已被官方禁用 35 | */ 36 | // isVip && vipReadTask(); 37 | await sleep(30000); 38 | } catch (error) { 39 | console.log(error); 40 | emailSend(error.message); 41 | } 42 | }; 43 | 44 | let scheduleApp = new Interval(); 45 | scheduleApp.create('主进程定时任务', '10 3 10 * * *', () => { 46 | try { 47 | const doTask = async (accountList) => { 48 | // accountList.length; 49 | for (let i = 0; i < 1; i++) { 50 | initHttpAxios(accountList[i].token); 51 | await doTaskHandle(accountList[i].vip); 52 | } 53 | }; 54 | redisConnectHandle(doTask); 55 | } catch (error) { 56 | console.log(error); 57 | emailSend(error); 58 | } 59 | }); 60 | 61 | scheduleApp.create('签到提醒任务', '10 0 22 * * *', () => { 62 | try { 63 | const doTask = async (accountList) => { 64 | for (let i = 0; i < 1; i++) { 65 | initHttpAxios(accountList[i].token); 66 | await doTaskSign(); 67 | } 68 | }; 69 | redisConnectHandle(doTask); 70 | } catch (error) { 71 | console.log(error); 72 | emailSend(error); 73 | } 74 | }); 75 | const doTaskSign = async () => { 76 | try { 77 | checkSign() 78 | await sleep(30000); 79 | } catch (error) { 80 | console.log(error); 81 | emailSend(error.message); 82 | } 83 | }; 84 | // 启动临时任务 85 | // let rule = new schedule.RecurrenceRule(); 86 | // rule.second = [0, 10, 20, 30, 40, 50]; // 每隔 10 秒执行一次 87 | 88 | // let job = schedule.scheduleJob(rule, () => { 89 | // try { 90 | // const doTask = async (accountList) => { 91 | // console.log(accountList[0].name); 92 | // for (let i = 0; i < accountList.length; i++) { 93 | // initHttpAxios(accountList[i].token); 94 | // await doTaskHandle(accountList[i].vip); 95 | // } 96 | // }; 97 | // redisConnectHandle(doTask); 98 | // } catch (error) { 99 | // console.log(error); 100 | // emailSend(error); 101 | // } 102 | // }); 103 | app.listen(port, () => { 104 | console.log(`app is running at http://127.0.0.1:${port}/`); 105 | }); 106 | -------------------------------------------------------------------------------- /eApp.js: -------------------------------------------------------------------------------- 1 | //app.js 2 | const express = require("express"); 3 | const cors = require("cors"); 4 | const nodemailer = require("nodemailer"); 5 | 6 | let nodeMail = nodemailer.createTransport({ 7 | service: "qq", //类型qq邮箱 8 | port: 465, //上文获取的port 9 | secure: true, //上文获取的secure 10 | auth: { 11 | user: "mingo233@qq.com", // 发送方的邮箱,可以选择你自己的qq邮箱 12 | pass: "jaixkznfbxgsbdbc", // 上文获取的stmp授权码 13 | }, 14 | }); 15 | 16 | const app = express(); 17 | app.use(express.json()); 18 | app.use(cors()); 19 | const email = "825740725@qq.com"; 20 | //发送邮件 21 | const mail = { 22 | from: `mingo233@qq.com`, // 发件人 23 | subject: "测试", //邮箱主题 24 | to: email, //收件人,这里由post请求传递过来 25 | // 邮件内容,用html格式编写 26 | html: ` 27 |

Mingo提醒:

28 |

今天已完成签到,为防止官方活跃检测,建议点击下面链接进入掘金页面

29 |

https://juejin.cn/

30 | `, 31 | }; 32 | //

您的验证码是:${code}

33 | nodeMail.sendMail(mail, (err, info) => { 34 | if (!err) { 35 | console.log(info); 36 | 37 | // res.json({ msg: "验证码发送成功" }); 38 | } else { 39 | // res.json({ msg: "验证码发送失败,请稍后重试" }); 40 | console.log(err); 41 | } 42 | }); 43 | 44 | app.listen(3001, () => { 45 | console.log("服务开启成功"); 46 | }); 47 | -------------------------------------------------------------------------------- /log/log-1.json: -------------------------------------------------------------------------------- 1 | {"err_no":15001,"err_msg":"您今日已完成签到,请勿重复签到","data":null} -------------------------------------------------------------------------------- /log/log-2.json: -------------------------------------------------------------------------------- 1 | {"err_no":15001,"err_msg":"您今日已完成签到,请勿重复签到","data":null} -------------------------------------------------------------------------------- /logs/2022-09-07.log: -------------------------------------------------------------------------------- 1 | { 2 | message: { name: 'aa', age: 99 }, 3 | level: 'info', 4 | label: 'juejin', 5 | timestamp: '2022-09-07 04:26:10.719 PM' 6 | } 7 | { 8 | message: { 9 | err_no: 1, 10 | err_msg: 'BaseResp Code:2 Message:booklet_shelf.go:79, method=code.byted.org/juejin/booklet_service/handlers.SubmitBookletReadingProgress,errCode=ERROR_NO_PARAM_ERROR err=', 11 | data: null 12 | }, 13 | level: 'info', 14 | label: 'juejin', 15 | timestamp: '2022-09-07 08:55:32.405 PM' 16 | level: 'info', 17 | label: 'juejin', 18 | timestamp: '2022-09-07 08:55:32.405 PM' 19 | } 20 | -------------------------------------------------------------------------------- /logs/2022-09-08.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mingo-233/automatic-check-script/ad21aac191426a188fde580735b521abf5ee0564/logs/2022-09-08.log -------------------------------------------------------------------------------- /logs/2022-09-18.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mingo-233/automatic-check-script/ad21aac191426a188fde580735b521abf5ee0564/logs/2022-09-18.log -------------------------------------------------------------------------------- /logs/2022-09-21.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mingo-233/automatic-check-script/ad21aac191426a188fde580735b521abf5ee0564/logs/2022-09-21.log -------------------------------------------------------------------------------- /logs/2022-09-22.log: -------------------------------------------------------------------------------- 1 | { 2 | message: { 3 | err_no: 0, 4 | err_msg: 'success', 5 | data: { incr_point: 4096, sum_point: 185337 } 6 | }, 7 | level: 'info', 8 | label: 'juejin', 9 | timestamp: '2022-09-22 08:01:10.270 AM' 10 | } 11 | { 12 | message: { 13 | err_no: 0, 14 | err_msg: 'success', 15 | data: { 16 | id: 18, 17 | lottery_id: '6981716405976743943', 18 | lottery_name: 'Bug', 19 | lottery_type: 2, 20 | lottery_image: 'https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0a4ce25d48b8405cbf5444b6195928d4~tplv-k3u1fbpfcp-no-mark:0:0:0:0.image', 21 | lottery_desc: '', 22 | lottery_cost: 0, 23 | history_id: '7145987468154634274', 24 | total_lucky_value: 5336, 25 | draw_lucky_value: 10 26 | } 27 | }, 28 | level: 'info', 29 | label: 'juejin', 30 | timestamp: '2022-09-22 08:01:10.572 AM' 31 | } 32 | { 33 | message: { 34 | err_no: 0, 35 | err_msg: 'success', 36 | data: { 37 | lotteries: [ 38 | { 39 | user_id: '1609340755118711', 40 | history_id: '7145682521303302152', 41 | user_name: '陈辰宸', 42 | user_avatar: 'https://p26-passport.byteacctimg.com/img/user-avatar/86d146585e7f36c9860888f96ef29f4e~300x300.image', 43 | lottery_name: '字节咖啡保温杯', 44 | lottery_image: 'https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fa31d47d93ce4793a50b664a3e032b18~tplv-k3u1fbpfcp-no-mark:0:0:0:0.image?', 45 | date: 1663733861, 46 | dip_lucky_user_count: 3257, 47 | dip_lucky_users: [ 48 | { 49 | user_id: '75222061691005', 50 | user_name: '用户7764100806', 51 | avatar_large: 'https://p6-passport.byteacctimg.com/img/user-avatar/0e95e6f86227319d55afa613be7ba8a5~300x300.image' 52 | }, 53 | { 54 | user_id: '254742430497751', 55 | user_name: 'Yxx01', 56 | avatar_large: 'https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/4/23/171a4b84cf4d96f9~tplv-t2oaga2asx-image.image' 57 | }, 58 | { 59 | user_id: '831686056870728', 60 | user_name: '星星7652', 61 | avatar_large: 'https://p3-passport.byteacctimg.com/img/mosaic-legacy/3795/3047680722~300x300.image' 62 | }, 63 | { 64 | user_id: '2133507821543879', 65 | user_name: '用户8496562525284', 66 | avatar_large: 'https://p9-passport.byteacctimg.com/img/mosaic-legacy/3793/3131589739~300x300.image' 67 | }, 68 | { 69 | user_id: '532627197277133', 70 | user_name: '指挥官乄', 71 | avatar_large: 'https://p6-passport.byteacctimg.com/img/user-avatar/65f2953efc7779724440463d8d4eecb4~300x300.image' 72 | }, 73 | { 74 | user_id: '3734356412862221', 75 | user_name: '乌龙茶风', 76 | avatar_large: 'https://p6-passport.byteacctimg.com/img/mosaic-legacy/3795/3033762272~300x300.image' 77 | }, 78 | { 79 | user_id: '2260251638763998', 80 | user_name: 'Breeze同学', 81 | avatar_large: 'https://p9-passport.byteacctimg.com/img/user-avatar/293596ee5eec685bdc10550400a0a0ea~300x300.image' 82 | }, 83 | { 84 | user_id: '286326221974173', 85 | user_name: '用户7761966292', 86 | avatar_large: 'https://p6-passport.byteacctimg.com/img/user-avatar/4074f1456f3507bceca40badb972308f~300x300.image' 87 | }, 88 | { 89 | user_id: '3158247505146359', 90 | user_name: 'Blame', 91 | avatar_large: 'https://p26-passport.byteacctimg.com/img/user-avatar/8180ac6030b58031939ecac5d0067ffd~300x300.image' 92 | }, 93 | { 94 | user_id: '2471357872607614', 95 | user_name: '朕在入港', 96 | avatar_large: 'https://p9-passport.byteacctimg.com/img/user-avatar/61326bad5b555c09f0dc62d6fbd08961~300x300.image' 97 | } 98 | ] 99 | }, 100 | { 101 | user_id: '3545243881060551', 102 | history_id: '7145378407000801288', 103 | user_name: '啊该用户不存在', 104 | user_avatar: 'https://p26-passport.byteacctimg.com/img/mosaic-legacy/3795/3033762272~300x300.image', 105 | lottery_name: '字节咖啡保温杯', 106 | lottery_image: 'https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fa31d47d93ce4793a50b664a3e032b18~tplv-k3u1fbpfcp-no-mark:0:0:0:0.image?', 107 | date: 1663663108, 108 | dip_lucky_user_count: 5850, 109 | dip_lucky_users: [ 110 | { 111 | user_id: '1121923259444189', 112 | user_name: '浪费西626', 113 | avatar_large: 'https://p6-passport.byteacctimg.com/img/mosaic-legacy/3793/3131589739~300x300.image' 114 | }, 115 | { 116 | user_id: '4248168662576446', 117 | user_name: '摸鱼之', 118 | avatar_large: 'https://p9-passport.byteacctimg.com/img/user-avatar/4c05eed314ec83b3bea3d8e6ee51e6fc~300x300.image' 119 | }, 120 | { 121 | user_id: '1425402138994237', 122 | user_name: 'AVOID_NPE', 123 | avatar_large: 'https://p6-passport.byteacctimg.com/img/user-avatar/3265693a5cfcb3ae7ff8832f7652c977~300x300.image' 124 | }, 125 | { 126 | user_id: '2876786657998744', 127 | user_name: '王瑞281', 128 | avatar_large: 'https://p3-passport.byteacctimg.com/img/user-avatar/f8d7d83ea3dc5db61584baf9b61779b0~300x300.image' 129 | }, 130 | { 131 | user_id: '2137887890214120', 132 | user_name: '清欢爱蹦跶', 133 | avatar_large: 'https://p3-passport.byteacctimg.com/img/user-avatar/4e5dc0ffd7b8080e614151fe93e61e41~300x300.image' 134 | }, 135 | { 136 | user_id: '128022378711965', 137 | user_name: 'biubiubiu66', 138 | avatar_large: 'https://p6-passport.byteacctimg.com/img/mosaic-legacy/3793/3114521287~300x300.image' 139 | }, 140 | { 141 | user_id: '3746751243622951', 142 | user_name: '膨胀男孩', 143 | avatar_large: 'https://p26-passport.byteacctimg.com/img/user-avatar/3dd47e854dcc77a16fbaf9715887e3a5~300x300.image' 144 | }, 145 | { 146 | user_id: '2867148445460718', 147 | user_name: 'Wynne', 148 | avatar_large: 'https://p9-passport.byteacctimg.com/img/user-avatar/d958acaffaf85f5849539a064ce46215~300x300.image' 149 | }, 150 | { 151 | user_id: '2277843824803965', 152 | user_name: '羽习习', 153 | avatar_large: 'https://p6-passport.byteacctimg.com/img/user-avatar/ddcb06aacecded98a724ffe2ceabf92d~300x300.image' 154 | }, 155 | { 156 | user_id: '1253923373721646', 157 | user_name: '朱伟2943', 158 | avatar_large: 'https://p9-passport.byteacctimg.com/img/user-avatar/1651e9d80184a86f0fa0061972875525~300x300.image' 159 | } 160 | ] 161 | }, 162 | { 163 | user_id: '3421335916645806', 164 | history_id: '7145036492027461667', 165 | user_name: '喵呼', 166 | user_avatar: 'https://p9-passport.byteacctimg.com/img/user-avatar/098e4fe103d08fa4a885d182ba332557~300x300.image', 167 | lottery_name: '字节咖啡保温杯', 168 | lottery_image: 'https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fa31d47d93ce4793a50b664a3e032b18~tplv-k3u1fbpfcp-no-mark:0:0:0:0.image?', 169 | date: 1663583732, 170 | dip_lucky_user_count: 6955, 171 | dip_lucky_users: [ 172 | { 173 | user_id: '1429831466884327', 174 | user_name: '君越', 175 | avatar_large: 'https://p26-passport.byteacctimg.com/img/mosaic-legacy/3795/3044413937~300x300.image' 176 | }, 177 | { 178 | user_id: '1183540868031021', 179 | user_name: '瀚莎', 180 | avatar_large: 'https://p6-passport.byteacctimg.com/img/user-avatar/42578f7778606d02639f76a344017459~300x300.image' 181 | }, 182 | { 183 | user_id: '1676109687039799', 184 | user_name: 'Sus', 185 | avatar_large: 'https://p26-passport.byteacctimg.com/img/user-avatar/df477141e030eba72a944a983937475d~300x300.image' 186 | }, 187 | { 188 | user_id: '3206620968128407', 189 | user_name: '用户1892712826183', 190 | avatar_large: 'https://p9-passport.byteacctimg.com/img/mosaic-legacy/3795/3044413937~300x300.image' 191 | }, 192 | { 193 | user_id: '1046390799613095', 194 | user_name: 'Jock', 195 | avatar_large: 'https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2018/3/26/162600753d4df59d~tplv-t2oaga2asx-image.image' 196 | }, 197 | { 198 | user_id: '2432573356910189', 199 | user_name: 'BestSum', 200 | avatar_large: 'https://p6-passport.byteacctimg.com/img/user-avatar/fc3f542dba72622688908b2787b86300~300x300.image' 201 | }, 202 | { 203 | user_id: '1072769194014221', 204 | user_name: '蛙蛙世界第一可爱', 205 | avatar_large: 'https://p6-passport.byteacctimg.com/img/user-avatar/5a1736a080ed5a597867c31a762c5bb1~300x300.image' 206 | }, 207 | { 208 | user_id: '1038354422468248', 209 | user_name: '代码农民工', 210 | avatar_large: 'https://p3-passport.byteacctimg.com/img/mosaic-legacy/3795/3047680722~300x300.image' 211 | }, 212 | { 213 | user_id: '202725480475015', 214 | user_name: 'IoI', 215 | avatar_large: 'https://p26-passport.byteacctimg.com/img/user-avatar/dc456a446ed009a75e3efef2c1fba387~300x300.image' 216 | }, 217 | { 218 | user_id: '3118651488607710', 219 | user_name: '乐le', 220 | avatar_large: 'https://p9-passport.byteacctimg.com/img/user-avatar/18d1afc4b11d4d489c48c57c3e954ad3~300x300.image' 221 | } 222 | ] 223 | }, 224 | { 225 | user_id: '1415826707326701', 226 | history_id: '7144895216900636680', 227 | user_name: '穿过繁华', 228 | user_avatar: 'https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2018/6/12/163f306aa3db6a24~tplv-t2oaga2asx-image.image', 229 | lottery_name: '九阳养生电热水壶', 230 | lottery_image: 'https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b4826808d48547fd966ceec758d8b7c5~tplv-k3u1fbpfcp-no-mark:0:0:0:0.image?', 231 | date: 1663550559, 232 | dip_lucky_user_count: 3770, 233 | dip_lucky_users: [ 234 | { 235 | user_id: '3280598428290295', 236 | user_name: '打工人在掘金', 237 | avatar_large: 'https://p26-passport.byteacctimg.com/img/user-avatar/e0798b5c038fbd7db243ce0c35f2c670~300x300.image' 238 | }, 239 | { 240 | user_id: '3122268756261864', 241 | user_name: '阿星Plus', 242 | avatar_large: 'https://p3-passport.byteacctimg.com/img/user-avatar/3cc33f54c52379926f4f24c4bc44ff4d~300x300.image' 243 | }, 244 | { 245 | user_id: '1178283593967373', 246 | user_name: 'SloppyJack', 247 | avatar_large: 'https://p6-passport.byteacctimg.com/img/user-avatar/20d2a9586e883e32f0ec97bcf4a77221~300x300.image' 248 | }, 249 | { 250 | user_id: '395479917012008', 251 | user_name: '解柒', 252 | avatar_large: 'https://p3-passport.byteacctimg.com/img/user-avatar/4148984546be5ae33fe190a7d192ba28~300x300.image' 253 | }, 254 | { 255 | user_id: '110431258292296', 256 | user_name: '胡春梅5419', 257 | avatar_large: 'https://p3-passport.byteacctimg.com/img/user-avatar/b4eca1a03c949f30b113f464d307d95b~300x300.image' 258 | }, 259 | { 260 | user_id: '676954894778008', 261 | user_name: '小熊猫jk8899', 262 | avatar_large: 'https://p3-passport.byteacctimg.com/img/user-avatar/a692705a85783ebd2c411a5017c5cec4~300x300.image' 263 | }, 264 | { 265 | user_id: '518625217492520', 266 | user_name: 'logic1486951766000', 267 | avatar_large: 'https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/6/6/17289cfa48bd55b4~tplv-t2oaga2asx-image.image' 268 | }, 269 | { 270 | user_id: '2208259628081037', 271 | user_name: '老鸨', 272 | avatar_large: 'https://p6-passport.byteacctimg.com/img/user-avatar/a02dd88706ad629629cedf59659e827b~300x300.image' 273 | }, 274 | { 275 | user_id: '3553254511227815', 276 | user_name: '淡若白纸', 277 | avatar_large: 'https://p26-passport.byteacctimg.com/img/user-avatar/e45e8d28f1306b0e30c91cd152fb8bc6~300x300.image' 278 | }, 279 | { 280 | user_id: '3734360393520632', 281 | user_name: '白凤倚剑归', 282 | avatar_large: 'https://p3-passport.byteacctimg.com/img/user-avatar/38ddfa52be921cd945b7cab68f743cca~300x300.image' 283 | } 284 | ] 285 | }, 286 | { 287 | user_id: '1099167359570488', 288 | history_id: '7144551731223855134', 289 | user_name: '进击的李小明', 290 | user_avatar: 'https://p3-passport.byteacctimg.com/img/user-avatar/4294d0e5535896632975d3253eb50c5f~300x300.image', 291 | lottery_name: '九阳养生电热水壶', 292 | lottery_image: 'https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b4826808d48547fd966ceec758d8b7c5~tplv-k3u1fbpfcp-no-mark:0:0:0:0.image?', 293 | date: 1663470584, 294 | dip_lucky_user_count: 5648, 295 | dip_lucky_users: [ 296 | { 297 | user_id: '3307798078435901', 298 | user_name: '云的犹豫', 299 | avatar_large: 'https://p6-passport.byteacctimg.com/img/user-avatar/75de6d0f13ccf92d144a237375f63068~300x300.image' 300 | }, 301 | { 302 | user_id: '629342049668248', 303 | user_name: '勇敢酷244', 304 | avatar_large: 'https://p3-passport.byteacctimg.com/img/mosaic-legacy/3795/3047680722~300x300.image' 305 | }, 306 | { 307 | user_id: '3866304278959549', 308 | user_name: '透过范262', 309 | avatar_large: 'https://p6-passport.byteacctimg.com/img/mosaic-legacy/3793/3131589739~300x300.image' 310 | }, 311 | { 312 | user_id: '589781337648110', 313 | user_name: '用户3172193994629', 314 | avatar_large: 'https://p9-passport.byteacctimg.com/img/mosaic-legacy/3791/5035712059~300x300.image' 315 | }, 316 | { 317 | user_id: '3752014209624807', 318 | user_name: '萧雪7947', 319 | avatar_large: 'https://p26-passport.byteacctimg.com/img/user-avatar/19bdfc6d301af0a28ec82957c3160789~300x300.image' 320 | }, 321 | { 322 | user_id: '157979791615623', 323 | user_name: 'OnePiece', 324 | avatar_large: 'https://p26-passport.byteacctimg.com/img/user-avatar/45ed7222fe6b933671f46edd16861d44~300x300.image' 325 | }, 326 | { 327 | user_id: '1881969231146696', 328 | user_name: 'pk_啾', 329 | avatar_large: 'https://p3-passport.byteacctimg.com/img/user-avatar/2e5f2e3192a26f7922ed7e463ccc8e36~300x300.image' 330 | }, 331 | { 332 | user_id: '518625217489949', 333 | user_name: 'ADark0915', 334 | avatar_large: 'https://p6-passport.byteacctimg.com/img/user-avatar/d12205fb129d718156799d267d085257~300x300.image' 335 | }, 336 | { 337 | user_id: '2832815186774279', 338 | user_name: 'cn0379_2', 339 | avatar_large: 'https://p26-passport.byteacctimg.com/img/mosaic-legacy/3793/3131589739~300x300.image' 340 | }, 341 | { 342 | user_id: '3966693684034861', 343 | user_name: 'zhaishuo', 344 | avatar_large: 'https://p6-passport.byteacctimg.com/img/user-avatar/2373d4e498009055e90f923bb0767e3b~300x300.image' 345 | } 346 | ] 347 | } 348 | ], 349 | count: 1000 350 | } 351 | }, 352 | level: 'info', 353 | label: 'juejin', 354 | timestamp: '2022-09-22 08:01:10.705 AM' 355 | } 356 | { 357 | message: { 358 | err_no: 0, 359 | err_msg: 'success', 360 | data: { dip_action: 1, has_dip: false, total_value: 5346, dip_value: 10 } 361 | }, 362 | level: 'info', 363 | label: 'juejin', 364 | timestamp: '2022-09-22 08:01:10.892 AM' 365 | } 366 | { 367 | message: { 368 | err_no: 0, 369 | err_msg: 'success', 370 | data: { incr_point: 100, sum_point: 389 } 371 | }, 372 | level: 'info', 373 | label: 'juejin', 374 | timestamp: '2022-09-22 08:01:10.968 AM' 375 | } 376 | { 377 | message: { 378 | err_no: 0, 379 | err_msg: 'success', 380 | data: { 381 | booklet: { 382 | booklet_id: '7050063811973218341', 383 | base_info: { 384 | id: 0, 385 | booklet_id: '7050063811973218341', 386 | title: '深入浅出 Vite', 387 | price: 4990, 388 | category_id: '6809637767543259144', 389 | status: 1, 390 | user_id: '430664257382462', 391 | verify_status: 4, 392 | summary: '系统化学习现代构建工具 Vite,提升前端工程化能力', 393 | cover_img: 'https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8cb255da618249d0857e6ecfc5017340~tplv-k3u1fbpfcp-watermark.image?', 394 | section_count: 28, 395 | section_ids: '7050063811994189855|7050063812044685343|7077834799208988675|7053634868994899975|7057872229589057576|7058853948060336163|7058854154738860066|7058947037877764137|7060398408430780431|7060405118163746850|7061788120680759331|7061854307863232547|7065976180125466638|7065976180020609032|7066601785166659620|7066611951547187214|7066612265536978981|7068105121523531806|7068105121615314952|7066612739912761352|7066614740574797832|7066617580068274207|7066617652487127077|7066617769776644126|7066613178028785700|7066613452235603982|7066614663533821983|7070419010021490702|7120588458145349640', 396 | is_finished: 1, 397 | ctime: 1642386948, 398 | mtime: 1663056711, 399 | put_on_time: 1649235586, 400 | pull_off_time: -62135596800, 401 | finished_time: -62135596800, 402 | recycle_bin_time: -62135596800, 403 | verify_time: -62135596800, 404 | submit_time: 1663056711, 405 | top_time: 1649235611, 406 | wechat_group_img: 'https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/876f8a0a3e914e9fb741641d63f8f928~tplv-k3u1fbpfcp-watermark.image?', 407 | wechat_group_desc: '', 408 | wechat_group_signal: 'Vite406', 409 | read_time: 72858, 410 | buy_count: 3042, 411 | course_type: 1, 412 | background_img: 'https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4f0be63544f24e5da59ced8854807b3d~tplv-k3u1fbpfcp-watermark.image?', 413 | is_distribution: 1, 414 | distribution_img: 'https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1eaed56ad91747119f006f425e408ee2~tplv-k3u1fbpfcp-zoom-1.image?', 415 | commission: 998, 416 | can_vip_borrow: false 417 | }, 418 | user_info: { 419 | user_id: '430664257382462', 420 | user_name: '神三元', 421 | company: '字节跳动', 422 | job_title: '公众号 「三元同学」', 423 | avatar_large: 'https://p9-passport.byteacctimg.com/img/user-avatar/d43ef01b284d645d6d7ab02c8dc32aaa~300x300.image', 424 | level: 7, 425 | description: '掘金小册《深入浅出 Vite》、《React Hooks 与 Immutable 数据流实战》作者,目前就职于字节跳动前端架构团队', 426 | followee_count: 77, 427 | follower_count: 33870, 428 | post_article_count: 50, 429 | digg_article_count: 451, 430 | got_digg_count: 32388, 431 | got_view_count: 1625599, 432 | post_shortmsg_count: 36, 433 | digg_shortmsg_count: 30, 434 | isfollowed: false, 435 | favorable_author: 1, 436 | power: 97702, 437 | study_point: 0, 438 | university: { university_id: '0', name: '', logo: '' }, 439 | major: { major_id: '0', parent_id: '0', name: '' }, 440 | student_status: 0, 441 | select_event_count: 0, 442 | select_online_course_count: 0, 443 | identity: 0, 444 | is_select_annual: true, 445 | select_annual_rank: 0, 446 | annual_list_type: 0, 447 | extraMap: {}, 448 | is_logout: 0, 449 | annual_info: [], 450 | account_amount: 0, 451 | user_growth_info: { 452 | user_id: 430664257382462, 453 | jpower: 97702, 454 | jscore: 1613.5, 455 | jpower_level: 7, 456 | jscore_level: 5, 457 | jscore_title: '先锋掘友', 458 | author_achievement_list: [ 1 ], 459 | vip_level: 0, 460 | vip_title: '', 461 | jscore_next_level_score: 4656510908468560000 462 | }, 463 | is_vip: false 464 | }, 465 | is_buy: true, 466 | reading_progress: { 467 | id: 0, 468 | booklet_id: '7050063811973218341', 469 | user_id: '3289337926283534', 470 | status: 1, 471 | buy_type: 1, 472 | reading_end: 0, 473 | reading_progress: 79, 474 | last_section_id: '7060405118163746850', 475 | has_update: 1, 476 | last_rtime: 1663682755, 477 | ctime: 1656157385, 478 | mtime: 1663795353, 479 | valid_begin_time: 0, 480 | valid_end_time: 0, 481 | borrow_times: 0 482 | }, 483 | section_updated_count: 28, 484 | is_new: false 485 | }, 486 | introduction: { 487 | id: 85821, 488 | section_id: '7050063811994189855', 489 | title: '小册介绍', 490 | user_id: '430664257382462', 491 | booklet_id: '7050063811973218341', 492 | status: 1, 493 | content: '

你将获得

\n' + 494 | '
    \n' + 495 | '
  • \n' + 496 | '

    5 大模块,详细梳理构建工具实战要点

    \n' + 497 | '
  • \n' + 498 | '
  • \n' + 499 | '

    深入底层,彻底搞懂 Vite 原理与实现

    \n' + 500 | '
  • \n' + 501 | '
  • \n' + 502 | '

    真实场景,攻克工程实践中的核心问题

    \n' + 503 | '
  • \n' + 504 | '
  • \n' + 505 | '

    从 0 到 1,手把手教你实现构建引擎

    \n' + 506 | '
  • \n' + 507 | '
\n' + 508 | '

作者介绍

\n' + 509 | '

20220323稀土掘金-「小册」《深入浅出 Vite 》视觉延展 - 改1_作者简介.jpg

\n' + 510 | '

目前深耕前端工程化构建领域,拥有大量的 Vite 工程化实战和性能优化经验,曾将 Vite 落地到抖音直播 PC 端等公司大型业务中,也是 Vite Contributor。

\n' + 511 | '

小册介绍

\n' + 512 | '

课程介绍(1624x623).png

\n' + 513 | '

随着前端技术的发展,构建工具所做的事情早已不仅仅是打包代码 那么简单,如今它已经贯穿了前端项目的整个生命周期,在前端工程化中占据了越来越核心的位置。而由于前端项目规模的日益庞大,传统的构建工具已经无法满足开发者们对 DX(Developer Experience,即开发者体验) 的要求。在使用传统工具的过程中,我们经常会遇到这样的问题:

\n' + 514 | '
    \n' + 515 | '
  • \n' + 516 | '

    漫长的项目启动时间。作为开发者,经常要等待几分钟的时间才能看到项目启动完成。

    \n' + 517 | '
  • \n' + 518 | '
  • \n' + 519 | '

    漫长的热更新时间。热更新要花费数秒的时间,代码改动后反馈非常迟钝,而且随着项目规模越来越大,热更新也越来越慢。

    \n' + 520 | '
  • \n' + 521 | '
\n' + 522 | '

为了解决上述的问题,Vite 在一系列方案中脱颖而出。相比于传统的构建工具,Vite 项目启动时间能缩短 90% 以上,热更新能得到毫秒级的响应,给构建性能带来了一个量级的提升,极大地解决了大家的开发体验痛点。而回看社区,最近的 JavaScript 生态的年度调查结果也显示,Vite 的开发满意度超过 98%,成为整个前端社区中最受瞩目的技术。

\n' + 523 | '

因此,如果你现在要学习构建工具,Vite 将会是一个更好的选择。但很多人在学习和应用 Vite 的过程中总会遇到下面的问题:

\n' + 524 | '
    \n' + 525 | '
  • \n' + 526 | '

    缺少系统化的学习资料,社区中绝大多数的文章仅仅只是教会你如何搭建一个简单的脚手架项目,不够系统和深入

    \n' + 527 | '
  • \n' + 528 | '
  • \n' + 529 | '

    不了解 Vite 相关的生态,遇到问题的时候不知道使用哪些插件或者解决方案

    \n' + 530 | '
  • \n' + 531 | '
  • \n' + 532 | '

    不熟悉 Esbuild、Rollup 这些底层构建引擎,遇到一些稍微需要定制的场景就感到捉襟见肘

    \n' + 533 | '
  • \n' + 534 | '
  • \n' + 535 | '

    不知道如何将 Vite 和其它前端工具结合,如 Babelcore-js

    \n' + 536 | '
  • \n' + 537 | '
  • \n' + 538 | '

    构建工具源码晦涩难懂,对更深一步的原理学习望而却步

    \n' + 539 | '
  • \n' + 540 | '
  • \n' + 541 | '

    ......

    \n' + 542 | '
  • \n' + 543 | '
\n' + 544 | '

在本课程中,我会给大家系统梳理 Vite 知识,并解决如上种种问题,带大家了解到前端工程的构建中究竟具备哪些知识和技能,不光是 Vite 本身的知识,也包括 Vite 底层所使用的 Esbuild、Rollup 双引擎、Babel 编译工具链、模块规范标准等一系列构建生态,从此掌握前端构建领域的第一性原理,完成从构建小白到高手的蜕变。

\n' + 545 | '

在本课程中,我分了五个部分来系统讲述:

\n' + 546 | '

深入浅出 Vite (2).png

\n' + 547 | '
    \n' + 548 | '
  1. \n' + 549 | '

    基础使用篇。 重新认识构建工具本身的价值,学会 Vite 基本的功能使用,让你能够独立搭建一个相对完整的脚手架项目。

    \n' + 550 | '
  2. \n' + 551 | '
  3. \n' + 552 | '

    双引擎篇。 这个部分我会给你详细介绍 Vite 的双引擎架构,并带你系统学习 Esbuild 和 Rollup 相关的内容,掌握最小必要知识,为后续的高级应用作铺垫。

    \n' + 553 | '
  4. \n' + 554 | '
  5. \n' + 555 | '

    高级应用篇。 你将学习到 Vite 的各种高级用法和构建性能优化手段,全程高能实战,每一节都有具体可运行的示例项目。

    \n' + 556 | '
  6. \n' + 557 | '
  7. \n' + 558 | '

    源码精读篇。 在这个部分我们将剖析 Vite 的核心源码,理解诸如JITProxy ModuleModule GraphHMR BoundaryPlugin Container 等源码中重要概念的作用及其底层实现。

    \n' + 559 | '
  8. \n' + 560 | '
  9. \n' + 561 | '

    手写实战篇。 一方面我们会手写 Vite 的开发时 no-bundle 服务,另一方面也会带你一步步完成一个生产环境打包工具(Bundler),从 AST 解析的功能开始,最终实现一个 Bundler。

    \n' + 562 | '
  10. \n' + 563 | '
\n' + 564 | '

小册最大的特点就是基于实战,理论固然重要,但更重要的是上手实操,几乎所有的知识点你都能找到对应的示例实现代码,在课程的最后一章甚至会手把手教你写一个构建引擎。正如这句话所说:

\n' + 565 | '
\n' + 566 | '

Talk is cheap, show me your code.

\n' + 567 | '
\n' + 568 | '

希望这本小册能够降低你学习构建工具的门槛,带你真正完成从入门到进阶的过程。虽然本小册并不能解决所有的问题,但我在小册中分享的个人学习经验,以及面对问题时的思维方式或许给你带来一些启发。

\n' + 569 | '

祝学习愉快,我们小册中见!

\n' + 570 | '

你会学到什么?

\n' + 571 | '
    \n' + 572 | '
  • \n' + 573 | '

    熟练掌握 Vite 的使用,独立搭建完整的前端工程环境以及工作流;

    \n' + 574 | '
  • \n' + 575 | '
  • \n' + 576 | '

    学会 Vite 插件开发及项目性能优化技巧,独立解决 Vite 使用过程中的核心难题;

    \n' + 577 | '
  • \n' + 578 | '
  • \n' + 579 | '

    深入掌握 Vite 底层用到的打包工具 EsbuildRollup,学会核心的打包配置和插件开发姿势;

    \n' + 580 | '
  • \n' + 581 | '
  • \n' + 582 | '

    彻底理解 Vite 实现原理,学会 Vite 背后的优秀架构思想核心实现

    \n' + 583 | '
  • \n' + 584 | '
  • \n' + 585 | '

    从 0 到 1 手写构建工具,掌握前端构建工具的原理。

    \n' + 586 | '
  • \n' + 587 | '
\n' + 588 | '

适宜人群

\n' + 589 | '
    \n' + 590 | '
  • 想要系统学习 Vite,提升竞争力,但没有接触过,也缺少项目经历
  • \n' + 591 | '
  • 想通过 Vite 作为团队或者个人项目的技术栈,但对 Vite 的核心概念和进阶用法一知半解
  • \n' + 592 | '
  • 想系统化学习 Rollup 和 Esbuild,理解构建工具的底层原理及源码实现,苦于学习门槛高
  • \n' + 593 | '
\n' + 594 | '

购买须知

\n' + 595 | '
    \n' + 596 | '
  1. 本小册为图文形式虚拟内容服务,购买成功概不退款;
  2. \n' + 597 | '
  3. 小册于 2022 年 04 月 06 日上线,全部章节现已完成更新;
  4. \n' + 598 | '
  5. 购买用户可享有永久阅读权限,可进入小册微信群,与作者互动;
  6. \n' + 599 | '
  7. 掘金小册版权归北京北比信息技术有限公司所有,任何机构、媒体、网站或个人未经本网协议授权不得转载、链接、转贴或以其他方式复制发布/发表,违者将依法追究责任;
  8. \n' + 600 | '
  9. 在掘金小册阅读过程中,如有任何问题,请邮件联系 xiaoce@xitu.io
  10. \n' + 601 | '
', 602 | draft_content: '', 603 | draft_title: '小册介绍', 604 | markdown_content: '', 605 | markdown_show: '', 606 | is_free: 0, 607 | read_time: 320, 608 | read_count: 0, 609 | comment_count: 0, 610 | ctime: 1641716764, 611 | mtime: 1641716764, 612 | is_update: 0, 613 | draft_read_time: 0, 614 | vid: '', 615 | reading_progress: null 616 | }, 617 | sections: [ 618 | { 619 | id: 85822, 620 | section_id: '7050063812044685343', 621 | title: '开篇 :让 Vite 助力你的前端工程化之路', 622 | user_id: '430664257382462', 623 | booklet_id: '7050063811973218341', 624 | status: 1, 625 | content: '', 626 | draft_content: '', 627 | draft_title: '开篇 :让 Vite 助力你的前端工程化之路', 628 | markdown_content: '', 629 | markdown_show: '', 630 | is_free: 1, 631 | read_time: 510, 632 | read_count: 13660, 633 | comment_count: 54, 634 | ctime: 1641716764, 635 | mtime: 1641716764, 636 | is_update: 0, 637 | draft_read_time: 0, 638 | vid: '', 639 | reading_progress: { 640 | id: 0, 641 | booklet_id: '7050063811973218341', 642 | user_id: '3289337926283534', 643 | section_id: '7050063812044685343', 644 | reading_end: 0, 645 | reading_progress: 100, 646 | reading_position: 17, 647 | has_update: 1, 648 | last_rtime: 1663481186, 649 | ctime: 1656236051, 650 | mtime: 1663718471 651 | } 652 | }, 653 | { 654 | id: 86674, 655 | section_id: '7077834799208988675', 656 | title: '模块标准:为什么 ESM 是前端模块化的未来?', 657 | user_id: '430664257382462', 658 | booklet_id: '7050063811973218341', 659 | status: 1, 660 | content: '', 661 | draft_content: '', 662 | draft_title: '模块标准:为什么 ESM 是前端模块化的未来?', 663 | markdown_content: '', 664 | markdown_show: '', 665 | is_free: 0, 666 | read_time: 1988, 667 | read_count: 8447, 668 | comment_count: 37, 669 | ctime: 1648023179, 670 | mtime: 1648023179, 671 | is_update: 0, 672 | draft_read_time: 0, 673 | vid: '', 674 | reading_progress: { 675 | id: 0, 676 | booklet_id: '7050063811973218341', 677 | user_id: '3289337926283534', 678 | section_id: '7077834799208988675', 679 | reading_end: 0, 680 | reading_progress: 100, 681 | reading_position: 0, 682 | has_update: 1, 683 | last_rtime: 1663682749, 684 | ctime: 1656247440, 685 | mtime: 1663718471 686 | } 687 | }, 688 | { 689 | id: 85932, 690 | section_id: '7053634868994899975', 691 | title: '快速上手: 如何用 Vite 从零搭建前端项目?', 692 | user_id: '430664257382462', 693 | booklet_id: '7050063811973218341', 694 | status: 1, 695 | content: '', 696 | draft_content: '', 697 | draft_title: '快速上手: 如何用 Vite 从零搭建前端项目?', 698 | markdown_content: '', 699 | markdown_show: '', 700 | is_free: 0, 701 | read_time: 1229, 702 | read_count: 8597, 703 | comment_count: 61, 704 | ctime: 1642328835, 705 | mtime: 1642328835, 706 | is_update: 0, 707 | draft_read_time: 0, 708 | vid: '', 709 | reading_progress: { 710 | id: 0, 711 | booklet_id: '7050063811973218341', 712 | user_id: '3289337926283534', 713 | section_id: '7053634868994899975', 714 | reading_end: 0, 715 | reading_progress: 100, 716 | reading_position: 0, 717 | has_update: 1, 718 | last_rtime: 1663682750, 719 | ctime: 1656247440, 720 | mtime: 1663718471 721 | } 722 | }, 723 | { 724 | id: 86098, 725 | section_id: '7057872229589057576', 726 | title: '样式方案:在 Vite 中接入现代化的 CSS 工程化方案', 727 | user_id: '430664257382462', 728 | booklet_id: '7050063811973218341', 729 | status: 1, 730 | content: '', 731 | draft_content: '', 732 | draft_title: '样式方案:在 Vite 中接入现代化的 CSS 工程化方案', 733 | markdown_content: '', 734 | markdown_show: '', 735 | is_free: 0, 736 | read_time: 2084, 737 | read_count: 8204, 738 | comment_count: 84, 739 | ctime: 1643517556, 740 | mtime: 1643517556, 741 | is_update: 0, 742 | draft_read_time: 0, 743 | vid: '', 744 | reading_progress: { 745 | id: 0, 746 | booklet_id: '7050063811973218341', 747 | user_id: '3289337926283534', 748 | section_id: '7057872229589057576', 749 | reading_end: 0, 750 | reading_progress: 100, 751 | reading_position: 0, 752 | has_update: 1, 753 | last_rtime: 1663414496, 754 | ctime: 1656249641, 755 | mtime: 1663718471 756 | } 757 | }, 758 | { 759 | id: 86100, 760 | section_id: '7058853948060336163', 761 | title: '代码规范: 如何利用 Lint 工具链来保证代码风格和质量?', 762 | user_id: '430664257382462', 763 | booklet_id: '7050063811973218341', 764 | status: 1, 765 | content: '', 766 | draft_content: '', 767 | draft_title: '代码规范: 如何利用 Lint 工具链来保证代码风格和质量?', 768 | markdown_content: '', 769 | markdown_show: '', 770 | is_free: 0, 771 | read_time: 2201, 772 | read_count: 7708, 773 | comment_count: 94, 774 | ctime: 1643539182, 775 | mtime: 1643539182, 776 | is_update: 0, 777 | draft_read_time: 0, 778 | vid: '', 779 | reading_progress: { 780 | id: 0, 781 | booklet_id: '7050063811973218341', 782 | user_id: '3289337926283534', 783 | section_id: '7058853948060336163', 784 | reading_end: 0, 785 | reading_progress: 100, 786 | reading_position: 0, 787 | has_update: 1, 788 | last_rtime: 1663414496, 789 | ctime: 1656249660, 790 | mtime: 1663718471 791 | } 792 | }, 793 | { 794 | id: 86101, 795 | section_id: '7058854154738860066', 796 | title: '静态资源: 如何在 Vite 中处理各种静态资源?', 797 | user_id: '430664257382462', 798 | booklet_id: '7050063811973218341', 799 | status: 1, 800 | content: '', 801 | draft_content: '', 802 | draft_title: '静态资源: 如何在 Vite 中处理各种静态资源?', 803 | markdown_content: '', 804 | markdown_show: '', 805 | is_free: 1, 806 | read_time: 2547, 807 | read_count: 8681, 808 | comment_count: 60, 809 | ctime: 1643634078, 810 | mtime: 1643634078, 811 | is_update: 0, 812 | draft_read_time: 0, 813 | vid: '', 814 | reading_progress: { 815 | id: 0, 816 | booklet_id: '7050063811973218341', 817 | user_id: '3289337926283534', 818 | section_id: '7058854154738860066', 819 | reading_end: 0, 820 | reading_progress: 100, 821 | reading_position: 0, 822 | has_update: 1, 823 | last_rtime: 1663414497, 824 | ctime: 1656467574, 825 | mtime: 1663414496 826 | } 827 | }, 828 | { 829 | id: 86102, 830 | section_id: '7058947037877764137', 831 | title: '预构建: 如何玩转秒级依赖预构建的能力?', 832 | user_id: '430664257382462', 833 | booklet_id: '7050063811973218341', 834 | status: 1, 835 | content: '', 836 | draft_content: '', 837 | draft_title: '预构建: 如何玩转秒级依赖预构建的能力?', 838 | markdown_content: '', 839 | markdown_show: '', 840 | is_free: 0, 841 | read_time: 1265, 842 | read_count: 5943, 843 | comment_count: 63, 844 | ctime: 1643727625, 845 | mtime: 1643727625, 846 | is_update: 0, 847 | draft_read_time: 0, 848 | vid: '', 849 | reading_progress: { 850 | id: 0, 851 | booklet_id: '7050063811973218341', 852 | user_id: '3289337926283534', 853 | section_id: '7058947037877764137', 854 | reading_end: 0, 855 | reading_progress: 100, 856 | reading_position: 0, 857 | has_update: 1, 858 | last_rtime: 1663414498, 859 | ctime: 1656668570, 860 | mtime: 1663414497 861 | } 862 | }, 863 | { 864 | id: 86113, 865 | section_id: '7060398408430780431', 866 | title: '双引擎架构: Vite 是如何站在巨人的肩膀上实现的?', 867 | user_id: '430664257382462', 868 | booklet_id: '7050063811973218341', 869 | status: 1, 870 | content: '', 871 | draft_content: '', 872 | draft_title: '双引擎架构: Vite 是如何站在巨人的肩膀上实现的?', 873 | markdown_content: '', 874 | markdown_show: '', 875 | is_free: 0, 876 | read_time: 799, 877 | read_count: 4916, 878 | comment_count: 38, 879 | ctime: 1643878668, 880 | mtime: 1643878668, 881 | is_update: 0, 882 | draft_read_time: 0, 883 | vid: '', 884 | reading_progress: { 885 | id: 0, 886 | booklet_id: '7050063811973218341', 887 | user_id: '3289337926283534', 888 | section_id: '7060398408430780431', 889 | reading_end: 0, 890 | reading_progress: 100, 891 | reading_position: 0, 892 | has_update: 1, 893 | last_rtime: 1663682752, 894 | ctime: 1656854321, 895 | mtime: 1663682750 896 | } 897 | }, 898 | { 899 | id: 86117, 900 | section_id: '7060405118163746850', 901 | title: '得力的性能推手: Esbuild 功能使用与插件开发实战', 902 | user_id: '430664257382462', 903 | booklet_id: '7050063811973218341', 904 | status: 1, 905 | content: '', 906 | draft_content: '', 907 | draft_title: '得力的性能推手: Esbuild 功能使用与插件开发实战', 908 | markdown_content: '', 909 | markdown_show: '', 910 | is_free: 0, 911 | read_time: 3199, 912 | read_count: 5255, 913 | comment_count: 51, 914 | ctime: 1644047375, 915 | mtime: 1644047375, 916 | is_update: 0, 917 | draft_read_time: 0, 918 | vid: '', 919 | reading_progress: { 920 | id: 0, 921 | booklet_id: '7050063811973218341', 922 | user_id: '3289337926283534', 923 | section_id: '7060405118163746850', 924 | reading_end: 0, 925 | reading_progress: 100, 926 | reading_position: 0, 927 | has_update: 1, 928 | last_rtime: 1663682755, 929 | ctime: 1656856344, 930 | mtime: 1663682752 931 | } 932 | }, 933 | { 934 | id: 86128, 935 | section_id: '7061788120680759331', 936 | title: 'Vite 构建基石(上)——Rollup 打包基本概念及使用', 937 | user_id: '430664257382462', 938 | booklet_id: '7050063811973218341', 939 | status: 1, 940 | content: '', 941 | draft_content: '', 942 | draft_title: 'Vite 构建基石(上)——Rollup 打包基本概念及使用', 943 | markdown_content: '', 944 | markdown_show: '', 945 | is_free: 0, 946 | read_time: 1853, 947 | read_count: 4295, 948 | comment_count: 15, 949 | ctime: 1644216084, 950 | mtime: 1644216084, 951 | is_update: 0, 952 | draft_read_time: 0, 953 | vid: '', 954 | reading_progress: { 955 | id: 0, 956 | booklet_id: '7050063811973218341', 957 | user_id: '3289337926283534', 958 | section_id: '7061788120680759331', 959 | reading_end: 0, 960 | reading_progress: 100, 961 | reading_position: 0, 962 | has_update: 1, 963 | last_rtime: 1663297717, 964 | ctime: 1656856661, 965 | mtime: 1663297715 966 | } 967 | }, 968 | { 969 | id: 86129, 970 | section_id: '7061854307863232547', 971 | title: 'Vite 构建基石(下)——深入理解 Rollup 的插件机制', 972 | user_id: '430664257382462', 973 | booklet_id: '7050063811973218341', 974 | status: 1, 975 | content: '', 976 | draft_content: '', 977 | draft_title: 'Vite 构建基石(下)——深入理解 Rollup 的插件机制', 978 | markdown_content: '', 979 | markdown_show: '', 980 | is_free: 0, 981 | read_time: 2368, 982 | read_count: 4102, 983 | comment_count: 26, 984 | ctime: 1644221068, 985 | mtime: 1644221068, 986 | is_update: 0, 987 | draft_read_time: 0, 988 | vid: '', 989 | reading_progress: { 990 | id: 0, 991 | booklet_id: '7050063811973218341', 992 | user_id: '3289337926283534', 993 | section_id: '7061854307863232547', 994 | reading_end: 0, 995 | reading_progress: 100, 996 | reading_position: 0, 997 | has_update: 1, 998 | last_rtime: 1663297721, 999 | ctime: 1656925341, 1000 | mtime: 1663295587 1001 | } 1002 | }, 1003 | { 1004 | id: 86299, 1005 | section_id: '7065976180125466638', 1006 | title: '插件开发与实战: 如何开发一个完整的 Vite 插件?', 1007 | user_id: '430664257382462', 1008 | booklet_id: '7050063811973218341', 1009 | status: 1, 1010 | content: '', 1011 | draft_content: '', 1012 | draft_title: '插件开发与实战: 如何开发一个完整的 Vite 插件?', 1013 | markdown_content: '', 1014 | markdown_show: '', 1015 | is_free: 0, 1016 | read_time: 2912, 1017 | read_count: 3700, 1018 | comment_count: 30, 1019 | ctime: 1645323809, 1020 | mtime: 1645323809, 1021 | is_update: 0, 1022 | draft_read_time: 0, 1023 | vid: '', 1024 | reading_progress: { 1025 | id: 0, 1026 | booklet_id: '7050063811973218341', 1027 | user_id: '3289337926283534', 1028 | section_id: '7065976180125466638', 1029 | reading_end: 0, 1030 | reading_progress: 100, 1031 | reading_position: 0, 1032 | has_update: 1, 1033 | last_rtime: 1663297724, 1034 | ctime: 1656990961, 1035 | mtime: 1663297721 1036 | } 1037 | }, 1038 | { 1039 | id: 86298, 1040 | section_id: '7065976180020609032', 1041 | title: 'HMR API 及原理:代码改动后,如何进行毫秒级别的局部更新?', 1042 | user_id: '430664257382462', 1043 | booklet_id: '7050063811973218341', 1044 | status: 1, 1045 | content: '', 1046 | draft_content: '', 1047 | draft_title: 'HMR API 及原理:代码改动后,如何进行毫秒级别的局部更新?', 1048 | markdown_content: '', 1049 | markdown_show: '', 1050 | is_free: 1, 1051 | read_time: 1897, 1052 | read_count: 3630, 1053 | comment_count: 19, 1054 | ctime: 1645321442, 1055 | mtime: 1645321442, 1056 | is_update: 0, 1057 | draft_read_time: 0, 1058 | vid: '', 1059 | reading_progress: { 1060 | id: 0, 1061 | booklet_id: '7050063811973218341', 1062 | user_id: '3289337926283534', 1063 | section_id: '7065976180020609032', 1064 | reading_end: 0, 1065 | reading_progress: 100, 1066 | reading_position: 0, 1067 | has_update: 1, 1068 | last_rtime: 1658124913, 1069 | ctime: 1656990965, 1070 | mtime: 1658122644 1071 | } 1072 | }, 1073 | { 1074 | id: 86300, 1075 | section_id: '7066601785166659620', 1076 | title: '代码分割:打包完产物体积太大,怎么拆包?', 1077 | user_id: '430664257382462', 1078 | booklet_id: '7050063811973218341', 1079 | status: 1, 1080 | content: '', 1081 | draft_content: '', 1082 | draft_title: '代码分割:打包完产物体积太大,怎么拆包?', 1083 | markdown_content: '', 1084 | markdown_show: '', 1085 | is_free: 0, 1086 | read_time: 2005, 1087 | read_count: 3045, 1088 | comment_count: 29, 1089 | ctime: 1645323882, 1090 | mtime: 1645323882, 1091 | is_update: 0, 1092 | draft_read_time: 0, 1093 | vid: '', 1094 | reading_progress: { 1095 | id: 0, 1096 | booklet_id: '7050063811973218341', 1097 | user_id: '3289337926283534', 1098 | section_id: '7066601785166659620', 1099 | reading_end: 0, 1100 | reading_progress: 100, 1101 | reading_position: 57, 1102 | has_update: 1, 1103 | last_rtime: 1663307890, 1104 | ctime: 1656852612, 1105 | mtime: 1663297724 1106 | } 1107 | }, 1108 | { 1109 | id: 86301, 1110 | section_id: '7066611951547187214', 1111 | title: '语法降级与Polyfill:联合前端编译工具链,消灭低版本浏览器兼容问题', 1112 | user_id: '430664257382462', 1113 | booklet_id: '7050063811973218341', 1114 | status: 1, 1115 | content: '', 1116 | draft_content: '', 1117 | draft_title: '语法降级与Polyfill:联合前端编译工具链,消灭低版本浏览器兼容问题', 1118 | markdown_content: '', 1119 | markdown_show: '', 1120 | is_free: 1, 1121 | read_time: 2221, 1122 | read_count: 3187, 1123 | comment_count: 38, 1124 | ctime: 1645323993, 1125 | mtime: 1645323993, 1126 | is_update: 0, 1127 | draft_read_time: 0, 1128 | vid: '', 1129 | reading_progress: { 1130 | id: 0, 1131 | booklet_id: '7050063811973218341', 1132 | user_id: '3289337926283534', 1133 | section_id: '7066611951547187214', 1134 | reading_end: 0, 1135 | reading_progress: 100, 1136 | reading_position: 0, 1137 | has_update: 1, 1138 | last_rtime: 1658212839, 1139 | ctime: 1657010153, 1140 | mtime: 1658212830 1141 | } 1142 | }, 1143 | { 1144 | id: 86302, 1145 | section_id: '7066612265536978981', 1146 | title: ' 预渲染:如何借助 Vite 搭建高可用的服务端渲染(SSR)工程?', 1147 | user_id: '430664257382462', 1148 | booklet_id: '7050063811973218341', 1149 | status: 1, 1150 | content: '', 1151 | draft_content: '', 1152 | draft_title: ' 预渲染:如何借助 Vite 搭建高可用的服务端渲染(SSR)工程?', 1153 | markdown_content: '', 1154 | markdown_show: '', 1155 | is_free: 1, 1156 | read_time: 4085, 1157 | read_count: 2197, 1158 | comment_count: 22, 1159 | ctime: 1645324095, 1160 | mtime: 1645324095, 1161 | is_update: 0, 1162 | draft_read_time: 0, 1163 | vid: '', 1164 | reading_progress: { 1165 | id: 0, 1166 | booklet_id: '7050063811973218341', 1167 | user_id: '3289337926283534', 1168 | section_id: '7066612265536978981', 1169 | reading_end: 0, 1170 | reading_progress: 0, 1171 | reading_position: 0, 1172 | has_update: 1, 1173 | last_rtime: 1658137091, 1174 | ctime: 1658137089, 1175 | mtime: 1658137089 1176 | } 1177 | }, 1178 | { 1179 | id: 86369, 1180 | section_id: '7068105121523531806', 1181 | title: '模块联邦: 如何实现优雅的跨应用代码共享?', 1182 | user_id: '430664257382462', 1183 | booklet_id: '7050063811973218341', 1184 | status: 1, 1185 | content: '', 1186 | draft_content: '', 1187 | draft_title: '模块联邦: 如何实现优雅的跨应用代码共享?', 1188 | markdown_content: '', 1189 | markdown_show: '', 1190 | is_free: 0, 1191 | read_time: 2450, 1192 | read_count: 2116, 1193 | comment_count: 27, 1194 | ctime: 1645711673, 1195 | mtime: 1645711673, 1196 | is_update: 0, 1197 | draft_read_time: 0, 1198 | vid: '', 1199 | reading_progress: { 1200 | id: 0, 1201 | booklet_id: '7050063811973218341', 1202 | user_id: '3289337926283534', 1203 | section_id: '7068105121523531806', 1204 | reading_end: 0, 1205 | reading_progress: 100, 1206 | reading_position: 7, 1207 | has_update: 1, 1208 | last_rtime: 1660293640, 1209 | ctime: 1658136482, 1210 | mtime: 1660293221 1211 | } 1212 | }, 1213 | { 1214 | id: 86370, 1215 | section_id: '7068105121615314952', 1216 | title: '再谈 ESM:高阶特性 & Pure ESM 时代', 1217 | user_id: '430664257382462', 1218 | booklet_id: '7050063811973218341', 1219 | status: 1, 1220 | content: '', 1221 | draft_content: '', 1222 | draft_title: '再谈 ESM:高阶特性 & Pure ESM 时代', 1223 | markdown_content: '', 1224 | markdown_show: '', 1225 | is_free: 0, 1226 | read_time: 1543, 1227 | read_count: 1697, 1228 | comment_count: 7, 1229 | ctime: 1645711754, 1230 | mtime: 1645711754, 1231 | is_update: 0, 1232 | draft_read_time: 0, 1233 | vid: '', 1234 | reading_progress: { 1235 | id: 0, 1236 | booklet_id: '7050063811973218341', 1237 | user_id: '3289337926283534', 1238 | section_id: '7068105121615314952', 1239 | reading_end: 0, 1240 | reading_progress: 100, 1241 | reading_position: 0, 1242 | has_update: 1, 1243 | last_rtime: 1658137093, 1244 | ctime: 1658135862, 1245 | mtime: 1658137093 1246 | } 1247 | }, 1248 | { 1249 | id: 86303, 1250 | section_id: '7066612739912761352', 1251 | title: '性能优化: 如何体系化地对 Vite 项目进行性能优化?', 1252 | user_id: '430664257382462', 1253 | booklet_id: '7050063811973218341', 1254 | status: 1, 1255 | content: '', 1256 | draft_content: '', 1257 | draft_title: '性能优化: 如何体系化地对 Vite 项目进行性能优化?', 1258 | markdown_content: '', 1259 | markdown_show: '', 1260 | is_free: 0, 1261 | read_time: 1790, 1262 | read_count: 1959, 1263 | comment_count: 8, 1264 | ctime: 1645324159, 1265 | mtime: 1645324159, 1266 | is_update: 0, 1267 | draft_read_time: 0, 1268 | vid: '', 1269 | reading_progress: { 1270 | id: 0, 1271 | booklet_id: '7050063811973218341', 1272 | user_id: '3289337926283534', 1273 | section_id: '7066612739912761352', 1274 | reading_end: 0, 1275 | reading_progress: 100, 1276 | reading_position: 94, 1277 | has_update: 1, 1278 | last_rtime: 1658221145, 1279 | ctime: 1658135858, 1280 | mtime: 1658220178 1281 | } 1282 | }, 1283 | { 1284 | id: 86307, 1285 | section_id: '7066614740574797832', 1286 | title: '配置解析服务:配置文件在 Vite 内部被转换成什么样子了?', 1287 | user_id: '430664257382462', 1288 | booklet_id: '7050063811973218341', 1289 | status: 1, 1290 | content: '', 1291 | draft_content: '', 1292 | draft_title: '配置解析服务:配置文件在 Vite 内部被转换成什么样子了?', 1293 | markdown_content: '', 1294 | markdown_show: '', 1295 | is_free: 0, 1296 | read_time: 2223, 1297 | read_count: 1849, 1298 | comment_count: 9, 1299 | ctime: 1645325120, 1300 | mtime: 1645325120, 1301 | is_update: 0, 1302 | draft_read_time: 0, 1303 | vid: '', 1304 | reading_progress: { 1305 | id: 0, 1306 | booklet_id: '7050063811973218341', 1307 | user_id: '3289337926283534', 1308 | section_id: '7066614740574797832', 1309 | reading_end: 0, 1310 | reading_progress: 100, 1311 | reading_position: 0, 1312 | has_update: 1, 1313 | last_rtime: 1663296964, 1314 | ctime: 1658140136, 1315 | mtime: 1663296062 1316 | } 1317 | }, 1318 | { 1319 | id: 86309, 1320 | section_id: '7066617580068274207', 1321 | title: '依赖预构建:Esbuild 打包功能如何被 Vite 玩出花来?', 1322 | user_id: '430664257382462', 1323 | booklet_id: '7050063811973218341', 1324 | status: 1, 1325 | content: '', 1326 | draft_content: '', 1327 | draft_title: '依赖预构建:Esbuild 打包功能如何被 Vite 玩出花来?', 1328 | markdown_content: '', 1329 | markdown_show: '', 1330 | is_free: 1, 1331 | read_time: 3510, 1332 | read_count: 2995, 1333 | comment_count: 15, 1334 | ctime: 1645325164, 1335 | mtime: 1645325164, 1336 | is_update: 0, 1337 | draft_read_time: 0, 1338 | vid: '', 1339 | reading_progress: { 1340 | id: 0, 1341 | booklet_id: '7050063811973218341', 1342 | user_id: '3289337926283534', 1343 | section_id: '7066617580068274207', 1344 | reading_end: 0, 1345 | reading_progress: 100, 1346 | reading_position: 0, 1347 | has_update: 1, 1348 | last_rtime: 1663297701, 1349 | ctime: 1658210387, 1350 | mtime: 1663296973 1351 | } 1352 | }, 1353 | { 1354 | id: 86310, 1355 | section_id: '7066617652487127077', 1356 | title: '插件流水线:从整体到局部,理解 Vite 的核心编译能力', 1357 | user_id: '430664257382462', 1358 | booklet_id: '7050063811973218341', 1359 | status: 1, 1360 | content: '', 1361 | draft_content: '', 1362 | draft_title: '插件流水线:从整体到局部,理解 Vite 的核心编译能力', 1363 | markdown_content: '', 1364 | markdown_show: '', 1365 | is_free: 0, 1366 | read_time: 4236, 1367 | read_count: 1762, 1368 | comment_count: 3, 1369 | ctime: 1645325174, 1370 | mtime: 1645325174, 1371 | is_update: 0, 1372 | draft_read_time: 0, 1373 | vid: '', 1374 | reading_progress: null 1375 | }, 1376 | { 1377 | id: 86311, 1378 | section_id: '7066617769776644126', 1379 | title: ' 热更新:基于 ESM 的毫秒级 HMR 的实现揭秘', 1380 | user_id: '430664257382462', 1381 | booklet_id: '7050063811973218341', 1382 | status: 1, 1383 | content: '', 1384 | draft_content: '', 1385 | draft_title: ' 热更新:基于 ESM 的毫秒级 HMR 的实现揭秘', 1386 | markdown_content: '', 1387 | markdown_show: '', 1388 | is_free: 0, 1389 | read_time: 3246, 1390 | read_count: 1730, 1391 | comment_count: 5, 1392 | ctime: 1645325199, 1393 | mtime: 1645325199, 1394 | is_update: 0, 1395 | draft_read_time: 0, 1396 | vid: '', 1397 | reading_progress: { 1398 | id: 0, 1399 | booklet_id: '7050063811973218341', 1400 | user_id: '3289337926283534', 1401 | section_id: '7066617769776644126', 1402 | reading_end: 0, 1403 | reading_progress: 100, 1404 | reading_position: 0, 1405 | has_update: 1, 1406 | last_rtime: 1658214504, 1407 | ctime: 1658214196, 1408 | mtime: 1658214196 1409 | } 1410 | }, 1411 | { 1412 | id: 86304, 1413 | section_id: '7066613178028785700', 1414 | title: '手写 Vite: 实现 no-bundle 开发服务(上)', 1415 | user_id: '430664257382462', 1416 | booklet_id: '7050063811973218341', 1417 | status: 1, 1418 | content: '', 1419 | draft_content: '', 1420 | draft_title: '手写 Vite: 实现 no-bundle 开发服务(上)', 1421 | markdown_content: '', 1422 | markdown_show: '', 1423 | is_free: 1, 1424 | read_time: 5486, 1425 | read_count: 2709, 1426 | comment_count: 35, 1427 | ctime: 1645324441, 1428 | mtime: 1645324441, 1429 | is_update: 0, 1430 | draft_read_time: 0, 1431 | vid: '', 1432 | reading_progress: null 1433 | }, 1434 | { 1435 | id: 86305, 1436 | section_id: '7066613452235603982', 1437 | title: '手写 Vite: 实现 no-bundle 开发服务(下)', 1438 | user_id: '430664257382462', 1439 | booklet_id: '7050063811973218341', 1440 | status: 1, 1441 | content: '', 1442 | draft_content: '', 1443 | draft_title: '手写 Vite: 实现 no-bundle 开发服务(下)', 1444 | markdown_content: '', 1445 | markdown_show: '', 1446 | is_free: 0, 1447 | read_time: 5567, 1448 | read_count: 1437, 1449 | comment_count: 10, 1450 | ctime: 1645324459, 1451 | mtime: 1645324459, 1452 | is_update: 0, 1453 | draft_read_time: 0, 1454 | vid: '', 1455 | reading_progress: { 1456 | id: 0, 1457 | booklet_id: '7050063811973218341', 1458 | user_id: '3289337926283534', 1459 | section_id: '7066613452235603982', 1460 | reading_end: 0, 1461 | reading_progress: 0, 1462 | reading_position: 0, 1463 | has_update: 1, 1464 | last_rtime: 1658220176, 1465 | ctime: 1658220174, 1466 | mtime: 1658220174 1467 | } 1468 | }, 1469 | { 1470 | id: 86306, 1471 | section_id: '7066614663533821983', 1472 | title: '手写 Bundler: 实现 JavaScript AST 解析器——词法分析、语义分析', 1473 | user_id: '430664257382462', 1474 | booklet_id: '7050063811973218341', 1475 | status: 1, 1476 | content: '', 1477 | draft_content: '', 1478 | draft_title: '手写 Bundler: 实现 JavaScript AST 解析器——词法分析、语义分析', 1479 | markdown_content: '', 1480 | markdown_show: '', 1481 | is_free: 0, 1482 | read_time: 3103, 1483 | read_count: 1285, 1484 | comment_count: 7, 1485 | ctime: 1645324758, 1486 | mtime: 1645324758, 1487 | is_update: 0, 1488 | draft_read_time: 0, 1489 | vid: '', 1490 | reading_progress: null 1491 | }, 1492 | { 1493 | id: 86413, 1494 | section_id: '7070419010021490702', 1495 | title: '手写 Bundler: 实现代码打包、 Tree Shaking ', 1496 | user_id: '430664257382462', 1497 | booklet_id: '7050063811973218341', 1498 | status: 1, 1499 | content: '', 1500 | draft_content: '', 1501 | draft_title: '手写 Bundler: 实现代码打包、 Tree Shaking ', 1502 | markdown_content: '', 1503 | markdown_show: '', 1504 | is_free: 0, 1505 | read_time: 6312, 1506 | read_count: 1420, 1507 | comment_count: 7, 1508 | ctime: 1646220146, 1509 | mtime: 1646220146, 1510 | is_update: 0, 1511 | draft_read_time: 0, 1512 | vid: '', 1513 | reading_progress: { 1514 | id: 0, 1515 | booklet_id: '7050063811973218341', 1516 | user_id: '3289337926283534', 1517 | section_id: '7070419010021490702', 1518 | reading_end: 0, 1519 | reading_progress: 16, 1520 | reading_position: 0, 1521 | has_update: 1, 1522 | last_rtime: 1658210387, 1523 | ctime: 1657001962, 1524 | mtime: 1658210383 1525 | } 1526 | }, 1527 | { 1528 | id: 87553, 1529 | section_id: '7120588458145349640', 1530 | title: '加餐: Vite 3.0 核心更新盘点与分析', 1531 | user_id: '430664257382462', 1532 | booklet_id: '7050063811973218341', 1533 | status: 1, 1534 | content: '', 1535 | draft_content: '', 1536 | draft_title: '加餐: Vite 3.0 核心更新盘点与分析', 1537 | markdown_content: '', 1538 | markdown_show: '', 1539 | is_free: 1, 1540 | read_time: 889, 1541 | read_count: 1029, 1542 | comment_count: 2, 1543 | ctime: 1657959737, 1544 | mtime: 1657959737, 1545 | is_update: 0, 1546 | draft_read_time: 0, 1547 | vid: '', 1548 | reading_progress: { 1549 | id: 0, 1550 | booklet_id: '7050063811973218341', 1551 | user_id: '3289337926283534', 1552 | section_id: '7120588458145349640', 1553 | reading_end: 0, 1554 | reading_progress: 100, 1555 | reading_position: 0, 1556 | has_update: 1, 1557 | last_rtime: 1663296973, 1558 | ctime: 1658210346, 1559 | mtime: 1663296969 1560 | } 1561 | } 1562 | ] 1563 | } 1564 | }, 1565 | level: 'info', 1566 | label: 'juejin', 1567 | timestamp: '2022-09-22 08:01:11.017 AM' 1568 | } 1569 | { 1570 | message: { 1571 | err_no: 0, 1572 | err_msg: 'success', 1573 | data: { 1574 | section: { 1575 | id: 85932, 1576 | section_id: '7053634868994899975', 1577 | title: '快速上手: 如何用 Vite 从零搭建前端项目?', 1578 | user_id: '430664257382462', 1579 | booklet_id: '7050063811973218341', 1580 | status: 1, 1581 | content: '', 1582 | draft_content: '', 1583 | draft_title: '快速上手: 如何用 Vite 从零搭建前端项目?', 1584 | markdown_content: '', 1585 | markdown_show: '', 1586 | is_free: 0, 1587 | read_time: 1229, 1588 | read_count: 8597, 1589 | comment_count: 61, 1590 | ctime: 1642328835, 1591 | mtime: 1642328835, 1592 | is_update: 0, 1593 | draft_read_time: 1229, 1594 | vid: '', 1595 | reading_progress: null 1596 | } 1597 | } 1598 | }, 1599 | level: 'info', 1600 | label: 'juejin', 1601 | timestamp: '2022-09-22 08:01:11.284 AM' 1602 | } 1603 | { 1604 | message: { 1605 | err_no: 0, 1606 | err_msg: 'success', 1607 | data: { 1608 | section: { 1609 | id: 86100, 1610 | section_id: '7058853948060336163', 1611 | title: '代码规范: 如何利用 Lint 工具链来保证代码风格和质量?', 1612 | user_id: '430664257382462', 1613 | booklet_id: '7050063811973218341', 1614 | status: 1, 1615 | content: '', 1616 | draft_content: '', 1617 | draft_title: '代码规范: 如何利用 Lint 工具链来保证代码风格和质量?', 1618 | markdown_content: '', 1619 | markdown_show: '', 1620 | is_free: 0, 1621 | read_time: 2201, 1622 | read_count: 7708, 1623 | comment_count: 94, 1624 | ctime: 1643539182, 1625 | mtime: 1643539182, 1626 | is_update: 0, 1627 | draft_read_time: 2201, 1628 | vid: '', 1629 | reading_progress: null 1630 | } 1631 | } 1632 | }, 1633 | level: 'info', 1634 | label: 'juejin', 1635 | timestamp: '2022-09-22 08:01:11.297 AM' 1636 | } 1637 | { 1638 | message: { 1639 | err_no: 0, 1640 | err_msg: 'success', 1641 | data: { 1642 | section: { 1643 | id: 86098, 1644 | section_id: '7057872229589057576', 1645 | title: '样式方案:在 Vite 中接入现代化的 CSS 工程化方案', 1646 | user_id: '430664257382462', 1647 | booklet_id: '7050063811973218341', 1648 | status: 1, 1649 | content: '', 1650 | draft_content: '', 1651 | draft_title: '样式方案:在 Vite 中接入现代化的 CSS 工程化方案', 1652 | markdown_content: '', 1653 | markdown_show: '', 1654 | is_free: 0, 1655 | read_time: 2084, 1656 | read_count: 8204, 1657 | comment_count: 84, 1658 | ctime: 1643517556, 1659 | mtime: 1643517556, 1660 | is_update: 0, 1661 | draft_read_time: 2084, 1662 | vid: '', 1663 | reading_progress: null 1664 | } 1665 | } 1666 | }, 1667 | level: 'info', 1668 | label: 'juejin', 1669 | timestamp: '2022-09-22 08:01:11.299 AM' 1670 | } 1671 | { 1672 | message: { 1673 | err_no: 0, 1674 | err_msg: 'success', 1675 | data: { 1676 | section: { 1677 | id: 86102, 1678 | section_id: '7058947037877764137', 1679 | title: '预构建: 如何玩转秒级依赖预构建的能力?', 1680 | user_id: '430664257382462', 1681 | booklet_id: '7050063811973218341', 1682 | status: 1, 1683 | content: '', 1684 | draft_content: '', 1685 | draft_title: '预构建: 如何玩转秒级依赖预构建的能力?', 1686 | markdown_content: '', 1687 | markdown_show: '', 1688 | is_free: 0, 1689 | read_time: 1265, 1690 | read_count: 5943, 1691 | comment_count: 63, 1692 | ctime: 1643727625, 1693 | mtime: 1643727625, 1694 | is_update: 0, 1695 | draft_read_time: 1265, 1696 | vid: '', 1697 | reading_progress: null 1698 | } 1699 | } 1700 | }, 1701 | level: 'info', 1702 | label: 'juejin', 1703 | timestamp: '2022-09-22 08:01:11.300 AM' 1704 | } 1705 | { 1706 | message: { 1707 | err_no: 0, 1708 | err_msg: 'success', 1709 | data: { 1710 | section: { 1711 | id: 85822, 1712 | section_id: '7050063812044685343', 1713 | title: '开篇 :让 Vite 助力你的前端工程化之路', 1714 | user_id: '430664257382462', 1715 | booklet_id: '7050063811973218341', 1716 | status: 1, 1717 | content: '

当下,在项目开发的过程中,前端工程师们越来越离不开构建工具了,可以说构建工具已经成为了前端工程项目的标配。

\n' + 1718 | '

不过,如今的前端构建工具可谓乱花渐欲迷人眼,有远古时代的browserifygrunt,有传统的WebpackRollupParcel,也有现代的EsbuildVite 等等,不仅种类繁多,更新也很快。

\n' + 1719 | '

于是,很多朋友会问我,到底哪个构建工具更好用、值得学。事实上,无论工具层面如何更新,它们解决的核心问题,即前端工程的痛点是不变的。因此,想要知道哪个工具更好用,就要看它解决前端工程痛点的效果。

\n' + 1720 | '

那么,前端工程都有哪些痛点呢?

\n' + 1721 | '

首先是前端的模块化需求。我们知道,业界的模块标准非常多,包括 ESM、CommonJS、AMD 和 CMD 等等。前端工程一方面需要落实这些模块规范,保证模块正常加载。另一方面需要兼容不同的模块规范,以适应不同的执行环境。

\n' + 1722 | '

其次是兼容浏览器,编译高级语法。由于浏览器的实现规范所限,只要高级语言/语法(TypeScript、 JSX 等)想要在浏览器中正常运行,就必须被转化为浏览器可以理解的形式。这都需要工具链层面的支持,而且这个需求会一直存在。

\n' + 1723 | '

再者是线上代码的质量问题。和开发阶段的考虑侧重点不同,生产环境中,我们不仅要考虑代码的安全性兼容性问题,保证线上代码的正常运行,也需要考虑代码运行时的性能问题。由于浏览器的版本众多,代码兼容性和安全策略各不相同,线上代码的质量问题也将是前端工程中长期存在的一个痛点。

\n' + 1724 | '

同时,开发效率也不容忽视。 我们知道,项目的冷启动/二次启动时间热更新时间都可能严重影响开发效率,尤其是当项目越来越庞大的时候。因此,提高项目的启动速度和热更新速度也是前端工程的重要需求。

\n' + 1725 | '

那么,前端构建工具是如何解决以上问题的呢?

\n' + 1726 | '

\n' + 1727 | '
    \n' + 1728 | '
  • \n' + 1729 | '

    模块化方面,提供模块加载方案,并兼容不同的模块规范。

    \n' + 1730 | '
  • \n' + 1731 | '
  • \n' + 1732 | '

    语法转译方面,配合 SassTSCBabel 等前端工具链,完成高级语法的转译功能,同时对于静态资源也能进行处理,使之能作为一个模块正常加载。

    \n' + 1733 | '
  • \n' + 1734 | '
  • \n' + 1735 | '

    产物质量方面,在生产环境中,配合 Terser等压缩工具进行代码压缩和混淆,通过 Tree Shaking 删除未使用的代码,提供对于低版本浏览器的语法降级处理等等。

    \n' + 1736 | '
  • \n' + 1737 | '
  • \n' + 1738 | '

    开发效率方面,构建工具本身通过各种方式来进行性能优化,包括使用原生语言 Go/Rustno-bundle等等思路,提高项目的启动性能和热更新的速度。

    \n' + 1739 | '
  • \n' + 1740 | '
\n' + 1741 | '

为什么 Vite 是当前最高效的构建工具?

\n' + 1742 | '

现在,让我们回到一开始提出的问题,到底哪个工具更好用?或者说,哪个工具解决前端工程痛点的效果更好?

\n' + 1743 | '

The State of JavaScript Survey 最近的调查结果中显示, Vite 在全球开发者中的满意度超过 98%,已经被用到了SvelteKitAstro这些大型框架中,成为当下最受瞩目的前端构建工具。我也最推荐你使用它。为什么是 Vite 呢?我们可以根据上面说的四个维度来审视它。

\n' + 1744 | '

首先是开发效率。传统构建工具普遍的缺点就是太慢了,与之相比,Vite 能将项目的启动性能提升一个量级,并且达到毫秒级的瞬间热更新效果。

\n' + 1745 | '

就拿 Webpack 来说,我在工作中发现,一般的项目使用 Webpack 之后,启动花个几分钟都是很常见的事情,热更新也经常需要等待十秒以上。这主要是因为:

\n' + 1746 | '
    \n' + 1747 | '
  • 项目冷启动时必须递归打包整个项目的依赖树
  • \n' + 1748 | '
  • JavaScript 语言本身的性能限制,导致构建性能遇到瓶颈,直接影响开发效率
  • \n' + 1749 | '
\n' + 1750 | '

这样一来,代码改动后不能立马看到效果,自然开发体验也越来越差。而其中,最占用时间的就是代码打包和文件编译。

\n' + 1751 | '

而 Vite 很好地解决了这些问题。一方面,Vite 在开发阶段基于浏览器原生 ESM 的支持实现了no-bundle服务,另一方面借助 Esbuild 超快的编译速度来做第三方库构建和 TS/JSX 语法编译,从而能够有效提高开发效率。

\n' + 1752 | '

除了开发效率,在其他三个维度上, Vite 也表现不俗。

\n' + 1753 | '
    \n' + 1754 | '
  • \n' + 1755 | '

    模块化方面,Vite 基于浏览器原生 ESM 的支持实现模块加载,并且无论是开发环境还是生产环境,都可以将其他格式的产物(如 CommonJS)转换为 ESM。

    \n' + 1756 | '
  • \n' + 1757 | '
  • \n' + 1758 | '

    语法转译方面,Vite 内置了对 TypeScript、JSX、Sass 等高级语法的支持,也能够加载各种各样的静态资源,如图片、Worker 等等。

    \n' + 1759 | '
  • \n' + 1760 | '
  • \n' + 1761 | '

    产物质量方面,Vite 基于成熟的打包工具 Rollup 实现生产环境打包,同时可以配合TerserBabel等工具链,可以极大程度保证构建产物的质量。

    \n' + 1762 | '
  • \n' + 1763 | '
\n' + 1764 | '

因此,如果你想要学习一个前端构建工具,Vite 将会是你当下一个最好的选择。它不仅解决了传统构建工具的开发效率问题,而且具备一个优秀构建工具的各项要素,还经历了社区大规模的验证与落地。

\n' + 1765 | '

如何才能学好 Vite ?

\n' + 1766 | '

不过,很多人在学习和应用 Vite 的过程中总会遇到各种各样的问题。

\n' + 1767 | '

比如说,很多 Vite 学习资料既不系统,也不深入。绝大多数的文章只能教会我们如何搭建一个简单的脚手架项目,甚至代码都不一定正确。

\n' + 1768 | '

即使通过资料学完了 Vite 的相关知识,但因为对 Vite 的生态了解不够,遇到实际问题的时候依然不知道要使用哪些插件或者解决方案。

\n' + 1769 | '
    \n' + 1770 | '
  • 第三方库里面含有 CommonJS 代码导致报错了怎么办?
  • \n' + 1771 | '
  • 想在开发过程中进行 Eslint 代码规范检查怎么办?
  • \n' + 1772 | '
  • 生产环境打包项目后,如何产出构建产物分析报告?
  • \n' + 1773 | '
  • 如果要兼容不支持原生 ESM 的浏览器,怎么办?
  • \n' + 1774 | '
\n' + 1775 | '

而且,如果你对 Vite 底层使用的构建引擎 Esbuild 和 Rollup 不够熟悉,遇到一些需要定制的场景,往往也会捉襟见肘。

\n' + 1776 | '
    \n' + 1777 | '
  • 写一个 Esbuild 插件来处理一下问题依赖
  • \n' + 1778 | '
  • 对于 Rollup 打包产物进行自定义拆包,解决实际场景中经常出现的循环依赖问题
  • \n' + 1779 | '
  • 使用 Esbuild 的代码转译和压缩功能会出现哪些兼容性问题?如何解决?
  • \n' + 1780 | '
\n' + 1781 | '

当然,作为一个构建工具,Vite 的难点不仅在于它本身的灵活性,也包含了诸如Babelcore-js 等诸多前端工具链的集成和应用。

\n' + 1782 | '
    \n' + 1783 | '
  • @babel/preset-envuseBuiltIns 属性各个取值有哪些区别?
  • \n' + 1784 | '
  • @babel/polyfill@babel/runtime-corejs 有什么区别?
  • \n' + 1785 | '
  • @babel/plugin-transform-runtime@babel/preset-envuseBuiltIn 相比有什么优化?
  • \n' + 1786 | '
  • core-js 的作用是什么?其产物有哪些版本?core-jscore-js-pure 有什么区别?
  • \n' + 1787 | '
\n' + 1788 | '

此外,由于构建工具(不仅包括 Vite,也包括底层引擎 Rollup)的源码晦涩难懂,涉及大量的基础工具库,导致很多人对构建工具原理的理解只浮于表面,很难更进一步。

\n' + 1789 | '

作为一名深耕在一线的前端工程师,我的日常工作就是跟各种构建工具打交道,在公司中诸多的业务项目中落地了 Vite,有丰富的 Vite 实战经验和源码阅读经验,也给 Vite 仓库贡献过一些代码。因此,我也非常乐意将自己在 Vite 方面的实战经验与学习方法通过小册系统性地分享给大家。

\n' + 1790 | '

20220323稀土掘金-「小册」《深入浅出 Vite 》视觉延展 - 改1_作者简介.jpg

\n' + 1791 | '

那么, Vite 该如何学习呢?我按照循序渐进、可实操、可延伸的三个原则,由浅入深设计课程内容,提供大量的实战场景和案例,同时尽可能给大家提供解决问题的方法和视角,让大家学完课程后能做到举一反三。具体来说,我将课程设计为 5 个模块。

\n' + 1792 | '

在基础使用篇中,我将与你从 0 开始实现 Vite 项目初始化,接入各种现代化的 CSS 方案,集成 Eslint、Styelint、Commonlint 等一系列 Lint 工具链,处理各种形式的静态资源,掌握 Vite 预编译的各种使用技巧,最终让你能独立搭建一个相对完整的脚手架工程。

\n' + 1793 | '

在双引擎篇中,我们会学习 Vite 的双引擎架构, Esbuild 和 Rollup 相关的内容,包括它们的基本使用和插件开发,掌握最小必要知识,为后续的高级应用作铺垫。

\n' + 1794 | '

而高级应用篇, 我们将学习 Vite 的各种高级用法和构建性能优化手段,学会如何编写一个完整的 Vite 插件,熟练进行生产环境拆包,使用 Vite 搭建复杂的 SSR 工程,实现基于模块联邦的跨应用模块共享架构。不管是项目性能优化技巧,还是对前端底层标准和规范的理解,你都会从这一模块得到不少提升。

\n' + 1795 | '

接下来,我们将一起剖析 Vite 的核心源码,理解诸如JITProxy ModuleModule GraphHMR BoundaryPlugin Container 等源码中重要概念的作用及底层实现,一步步教你学会阅读 Vite 的源码,将如下架构图中的关键环节各个击破,学透 Vite 实现原理。

\n' + 1796 | '

image.png

\n' + 1797 | '

最后是手写实战篇。 首先,我们会手写 Vite 的开发时 no-bundle 服务,也就是开发环境下基于浏览器原生 ESM 的 Dev Server。然后,我也会带你一步步完成一个生产环境打包工具(Bundler),从 AST 解析的功能开始,完成代码的词法分析(tokenize)和语义分析(parse),实现模块依赖图和作用域链的搭建,并完成 Tree Shaking、循环依赖检测及 Bundle 代码生成,最终实现一个类似 Rollup 的 Bundler。

\n' + 1798 | '

深入浅出 Vite (2).png

\n' + 1799 | '

可以看到,我们在课程中非常重视上手实战。课程的代码全部会上传至 Github 仓库(仓库地址),基本上每一节内容都有能 run 起来的代码案例。尤其在最后一章,为了让你理解构建工具的底层原理,我会带你一步步搭建一个简单的构建工具,进行上千行代码的手写实战,做到真正的代码可实操。

\n' + 1800 | '

最后,我希望在这本小册中,我们能一起深入 Vite 的实战要点和实现原理 ,领略前端工程化构建领域的底层风光,真正实现 Vite 从入门到进阶!

', 1801 | draft_content: '', 1802 | draft_title: '开篇 :让 Vite 助力你的前端工程化之路', 1803 | markdown_content: '', 1804 | markdown_show: '当下,在项目开发的过程中,前端工程师们越来越离不开构建工具了,可以说**构建工具已经成为了前端工程项目的标配。**\n' + 1805 | '\n' + 1806 | '不过,如今的前端构建工具可谓`乱花渐欲迷人眼`,有远古时代的`browserify`、`grunt`,有传统的`Webpack`、`Rollup`、`Parcel`,也有现代的`Esbuild`、`Vite` 等等,不仅种类繁多,更新也很快。\n' + 1807 | '\n' + 1808 | '于是,很多朋友会问我,到底哪个构建工具更好用、值得学。事实上,**无论工具层面如何更新,它们解决的核心问题,即前端工程的痛点是不变的**。因此,想要知道哪个工具更好用,就要看它解决前端工程痛点的效果。\n' + 1809 | '\n' + 1810 | '那么,前端工程都有哪些痛点呢?\n' + 1811 | '\n' + 1812 | '首先是前端的**模块化需求**。我们知道,业界的模块标准非常多,包括 ESM、CommonJS、AMD 和 CMD 等等。前端工程一方面需要落实这些模块规范,保证模块正常加载。另一方面需要兼容不同的模块规范,以适应不同的执行环境。\n' + 1813 | '\n' + 1814 | '其次是**兼容浏览器,编译高级语法**。由于浏览器的实现规范所限,只要高级语言/语法(TypeScript、 JSX 等)想要在浏览器中正常运行,就必须被转化为浏览器可以理解的形式。这都需要工具链层面的支持,而且这个需求会一直存在。\n' + 1815 | '\n' + 1816 | '再者是**线上代码的质量**问题。和开发阶段的考虑侧重点不同,生产环境中,我们不仅要考虑代码的`安全性`、`兼容性`问题,保证线上代码的正常运行,也需要考虑代码运行时的性能问题。由于浏览器的版本众多,代码兼容性和安全策略各不相同,线上代码的质量问题也将是前端工程中长期存在的一个痛点。\n' + 1817 | '\n' + 1818 | '同时,`开发效率`**也不容忽视。** 我们知道,**项目的冷启动/二次启动时间**、**热更新时间**都可能严重影响开发效率,尤其是当项目越来越庞大的时候。因此,提高项目的启动速度和热更新速度也是前端工程的重要需求。\n' + 1819 | '\n' + 1820 | '那么,前端构建工具是如何解决以上问题的呢?\n' + 1821 | '\n' + 1822 | '![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f54b17dcae4c49adb558b760048c3603~tplv-k3u1fbpfcp-zoom-1.image)\n' + 1823 | '\n' + 1824 | '- 模块化方面,提供模块加载方案,并兼容不同的模块规范。\n' + 1825 | '\n' + 1826 | '\n' + 1827 | '\n' + 1828 | '- 语法转译方面,配合 `Sass`、`TSC`、`Babel` 等前端工具链,完成高级语法的转译功能,同时对于静态资源也能进行处理,使之能作为一个模块正常加载。\n' + 1829 | '\n' + 1830 | '\n' + 1831 | '\n' + 1832 | '- 产物质量方面,在生产环境中,配合 `Terser`等压缩工具进行代码压缩和混淆,通过 `Tree Shaking` 删除未使用的代码,提供对于低版本浏览器的语法降级处理等等。\n' + 1833 | '\n' + 1834 | '\n' + 1835 | '\n' + 1836 | '- 开发效率方面,构建工具本身通过各种方式来进行性能优化,包括`使用原生语言 Go/Rust`、`no-bundle`等等思路,提高项目的启动性能和热更新的速度。\n' + 1837 | '\n' + 1838 | '## 为什么 Vite 是当前最高效的构建工具?\n' + 1839 | '\n' + 1840 | '现在,让我们回到一开始提出的问题,到底哪个工具更好用?或者说,哪个工具解决前端工程痛点的效果更好?\n' + 1841 | '\n' + 1842 | 'The State of JavaScript Survey 最近的调查结果中显示, Vite 在全球开发者中的满意度超过 98%,已经被用到了`SvelteKit`、`Astro`这些大型框架中,成为当下最受瞩目的前端构建工具。我也最推荐你使用它。为什么是 Vite 呢?我们可以根据上面说的四个维度来审视它。\n' + 1843 | '\n' + 1844 | '首先是开发效率。传统构建工具普遍的缺点就是太慢了,与之相比,Vite 能将项目的启动性能提升一个量级,并且达到毫秒级的瞬间热更新效果。\n' + 1845 | '\n' + 1846 | '就拿 Webpack 来说,我在工作中发现,一般的项目使用 Webpack 之后,启动花个几分钟都是很常见的事情,热更新也经常需要等待十秒以上。这主要是因为:\n' + 1847 | '\n' + 1848 | '- 项目冷启动时必须递归打包整个项目的依赖树\n' + 1849 | '- JavaScript 语言本身的性能限制,导致构建性能遇到瓶颈,直接影响开发效率\n' + 1850 | '\n' + 1851 | '这样一来,代码改动后不能立马看到效果,自然开发体验也越来越差。而其中,最占用时间的就是代码打包和文件编译。\n' + 1852 | '\n' + 1853 | '而 Vite 很好地解决了这些问题。一方面,Vite 在开发阶段基于浏览器原生 ESM 的支持实现了`no-bundle`服务,另一方面借助 Esbuild 超快的编译速度来做第三方库构建和 TS/JSX 语法编译,从而能够有效提高开发效率。\n' + 1854 | '\n' + 1855 | '除了开发效率,在其他三个维度上, Vite 也表现不俗。\n' + 1856 | '\n' + 1857 | '- 模块化方面,Vite 基于浏览器原生 ESM 的支持实现模块加载,并且无论是开发环境还是生产环境,都可以将其他格式的产物(如 CommonJS)转换为 ESM。\n' + 1858 | '\n' + 1859 | '- 语法转译方面,Vite 内置了对 TypeScript、JSX、Sass 等高级语法的支持,也能够加载各种各样的静态资源,如图片、Worker 等等。\n' + 1860 | '\n' + 1861 | '- 产物质量方面,Vite 基于成熟的打包工具 Rollup 实现生产环境打包,同时可以配合`Terser`、`Babel`等工具链,可以极大程度保证构建产物的质量。\n' + 1862 | '\n' + 1863 | '因此,如果你想要学习一个前端构建工具,Vite 将会是你当下一个最好的选择。它不仅解决了传统构建工具的开发效率问题,而且具备一个优秀构建工具的各项要素,还经历了社区大规模的验证与落地。\n' + 1864 | '\n' + 1865 | '## 如何才能学好 Vite ?\n' + 1866 | '\n' + 1867 | '不过,很多人在学习和应用 Vite 的过程中总会遇到各种各样的问题。\n' + 1868 | '\n' + 1869 | '比如说,很多 Vite 学习资料既不系统,也不深入。绝大多数的文章只能教会我们如何搭建一个简单的脚手架项目,甚至代码都不一定正确。\n' + 1870 | '\n' + 1871 | '即使通过资料学完了 Vite 的相关知识,但因为对 Vite 的生态了解不够,遇到实际问题的时候依然不知道要使用哪些插件或者解决方案。\n' + 1872 | '\n' + 1873 | '- 第三方库里面含有 CommonJS 代码导致报错了怎么办?\n' + 1874 | '- 想在开发过程中进行 Eslint 代码规范检查怎么办?\n' + 1875 | '- 生产环境打包项目后,如何产出构建产物分析报告?\n' + 1876 | '- 如果要兼容不支持原生 ESM 的浏览器,怎么办?\n' + 1877 | '\n' + 1878 | '而且,如果你对 Vite 底层使用的构建引擎 Esbuild 和 Rollup 不够熟悉,遇到一些需要定制的场景,往往也会捉襟见肘。\n' + 1879 | '\n' + 1880 | '- 写一个 Esbuild 插件来处理一下问题依赖\n' + 1881 | '- 对于 Rollup 打包产物进行自定义拆包,解决实际场景中经常出现的循环依赖问题\n' + 1882 | '- 使用 Esbuild 的代码转译和压缩功能会出现哪些兼容性问题?如何解决?\n' + 1883 | '\n' + 1884 | '当然,作为一个构建工具,Vite 的难点不仅在于它本身的灵活性,也包含了诸如`Babel`、`core-js` 等诸多前端工具链的集成和应用。\n' + 1885 | '\n' + 1886 | '- `@babel/preset-env` 的 `useBuiltIns` 属性各个取值有哪些区别?\n' + 1887 | '- `@babel/polyfill` 与 `@babel/runtime-corejs` 有什么区别?\n' + 1888 | '- `@babel/plugin-transform-runtime` 与`@babel/preset-env` 的 `useBuiltIn` 相比有什么优化?\n' + 1889 | '- core-js 的作用是什么?其产物有哪些版本?`core-js` 和 `core-js-pure` 有什么区别?\n' + 1890 | '\n' + 1891 | '此外,由于构建工具(不仅包括 Vite,也包括底层引擎 Rollup)的源码晦涩难懂,涉及大量的基础工具库,导致很多人对构建工具原理的理解只浮于表面,很难更进一步。\n' + 1892 | '\n' + 1893 | '作为一名深耕在一线的前端工程师,我的日常工作就是跟各种构建工具打交道,在公司中诸多的业务项目中落地了 Vite,有丰富的 Vite 实战经验和源码阅读经验,也给 Vite 仓库贡献过一些代码。因此,我也非常乐意将自己在 Vite 方面的实战经验与学习方法通过小册系统性地分享给大家。\n' + 1894 | '\n' + 1895 | '\n' + 1896 | '![20220323稀土掘金-「小册」《深入浅出 Vite 》视觉延展 - 改1_作者简介.jpg](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/112094547231465195123dabcc084ff8~tplv-k3u1fbpfcp-watermark.image?)\n' + 1897 | '\n' + 1898 | '\n' + 1899 | '那么, Vite 该如何学习呢?我按照**循序渐进、可实操、可延伸**的三个原则,由浅入深设计课程内容,提供大量的实战场景和案例,同时尽可能给大家提供解决问题的方法和视角,让大家学完课程后能做到举一反三。具体来说,我将课程设计为 5 个模块。\n' + 1900 | '\n' + 1901 | '**在基础使用篇中**,我将与你从 0 开始实现 Vite 项目初始化,接入各种现代化的 CSS 方案,集成 Eslint、Styelint、Commonlint 等一系列 Lint 工具链,处理各种形式的静态资源,掌握 Vite 预编译的各种使用技巧,最终让你能独立搭建一个相对完整的脚手架工程。\n' + 1902 | '\n' + 1903 | '**在双引擎篇中**,我们会学习 Vite 的双引擎架构, Esbuild 和 Rollup 相关的内容,包括它们的基本使用和插件开发,掌握`最小必要知识`,为后续的高级应用作铺垫。\n' + 1904 | '\n' + 1905 | '**而高级应用篇**, 我们将学习 Vite 的各种高级用法和构建性能优化手段,学会如何编写一个完整的 Vite 插件,熟练进行生产环境拆包,使用 Vite 搭建复杂的 SSR 工程,实现基于模块联邦的跨应用模块共享架构。不管是项目性能优化技巧,还是对前端底层标准和规范的理解,你都会从这一模块得到不少提升。\n' + 1906 | '\n' + 1907 | '接下来,我们将一起剖析 **Vite** **的核心源码**,理解诸如`JIT`、`Proxy Module`、`Module Graph`、`HMR Boundary`和`Plugin Container` 等源码中重要概念的作用及底层实现,一步步教你学会阅读 Vite 的源码,将如下架构图中的关键环节各个击破,学透 Vite 实现原理。\n' + 1908 | '\n' + 1909 | '![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/02910cd2c6894bcdb3a9e0fc9e59f4c2~tplv-k3u1fbpfcp-watermark.image?)\n' + 1910 | '\n' + 1911 | '**最后是手写实战篇。** 首先,我们会手写 Vite 的开发时 no-bundle 服务,也就是开发环境下基于浏览器原生 ESM 的 Dev Server。然后,我也会带你一步步完成一个生产环境打包工具(Bundler),从 AST 解析的功能开始,完成代码的词法分析(tokenize)和语义分析(parse),实现模块依赖图和作用域链的搭建,并完成 Tree Shaking、循环依赖检测及 Bundle 代码生成,最终实现一个类似 Rollup 的 Bundler。\n' + 1912 | '\n' + 1913 | '\n' + 1914 | '![深入浅出 Vite (2).png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/52599ad0dbb344d59eafb00f360e99c3~tplv-k3u1fbpfcp-watermark.image?)\n' + 1915 | '\n' + 1916 | '可以看到,我们在课程中非常重视上手实战。课程的代码全部会上传至 Github 仓库([仓库地址](https://github.com/sanyuan0704/juejin-book-vite)),基本上每一节内容都有能 run 起来的代码案例。尤其在最后一章,为了让你理解构建工具的底层原理,我会带你一步步搭建一个简单的构建工具,进行上千行代码的手写实战,做到真正的代码可实操。\n' + 1917 | '\n' + 1918 | '最后,我希望在这本小册中,我们能一起深入 Vite 的实战要点和实现原理 ,领略前端工程化构建领域的底层风光,真正实现 Vite 从入门到进阶!\n' + 1919 | '\n' + 1920 | '\n' + 1921 | '\n', 1922 | is_free: 1, 1923 | read_time: 510, 1924 | read_count: 13660, 1925 | comment_count: 54, 1926 | ctime: 1641716764, 1927 | mtime: 1641716764, 1928 | is_update: 0, 1929 | draft_read_time: 510, 1930 | vid: '', 1931 | reading_progress: null 1932 | } 1933 | } 1934 | }, 1935 | level: 'info', 1936 | label: 'juejin', 1937 | timestamp: '2022-09-22 08:01:11.308 AM' 1938 | } 1939 | { 1940 | message: { 1941 | err_no: 0, 1942 | err_msg: 'success', 1943 | data: { 1944 | section: { 1945 | id: 86674, 1946 | section_id: '7077834799208988675', 1947 | title: '模块标准:为什么 ESM 是前端模块化的未来?', 1948 | user_id: '430664257382462', 1949 | booklet_id: '7050063811973218341', 1950 | status: 1, 1951 | content: '', 1952 | draft_content: '', 1953 | draft_title: '模块标准:为什么 ESM 是前端模块化的未来?', 1954 | markdown_content: '', 1955 | markdown_show: '', 1956 | is_free: 0, 1957 | read_time: 1988, 1958 | read_count: 8447, 1959 | comment_count: 37, 1960 | ctime: 1648023179, 1961 | mtime: 1648023179, 1962 | is_update: 0, 1963 | draft_read_time: 1988, 1964 | vid: '', 1965 | reading_progress: null 1966 | } 1967 | } 1968 | }, 1969 | level: 'info', 1970 | label: 'juejin', 1971 | timestamp: '2022-09-22 08:01:11.314 AM' 1972 | } 1973 | { 1974 | message: { 1975 | err_no: 0, 1976 | err_msg: 'success', 1977 | data: { 1978 | id: 19, 1979 | lottery_id: '6981716980386496552', 1980 | lottery_name: '125矿石', 1981 | lottery_type: 1, 1982 | lottery_image: 'https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/32ed6a7619934144882d841761b63d3c~tplv-k3u1fbpfcp-no-mark:0:0:0:0.image', 1983 | lottery_desc: '', 1984 | lottery_cost: 0, 1985 | history_id: '7145987473837916194', 1986 | total_lucky_value: 30, 1987 | draw_lucky_value: 10 1988 | } 1989 | }, 1990 | level: 'info', 1991 | label: 'juejin', 1992 | timestamp: '2022-09-22 08:01:11.330 AM' 1993 | } 1994 | { 1995 | message: { 1996 | err_no: 0, 1997 | err_msg: 'success', 1998 | data: { 1999 | section: { 2000 | id: 86101, 2001 | section_id: '7058854154738860066', 2002 | title: '静态资源: 如何在 Vite 中处理各种静态资源?', 2003 | user_id: '430664257382462', 2004 | booklet_id: '7050063811973218341', 2005 | status: 1, 2006 | content: '

静态资源处理是前端工程经常遇到的问题,在真实的工程中不仅仅包含了动态执行的代码,也不可避免地要引入各种静态资源,如图片JSONWorker 文件Web Assembly 文件等等。

\n' + 2007 | '

而静态资源本身并不是标准意义上的模块,因此对它们的处理和普通的代码是需要区别对待的。一方面我们需要解决资源加载的问题,对 Vite 来说就是如何将静态资源解析并加载为一个 ES 模块的问题;另一方面在生产环境下我们还需要考虑静态资源的部署问题、体积问题、网络性能问题,并采取相应的方案来进行优化。

\n' + 2008 | '

在本小节,我将与你就这两方面的问题展开探讨,结合 Vite 自身的能力及其生态,来解决项目中静态资源处理的各个疑难点,同时也能继续完善目前的 Vite 脚手架工程。

\n' + 2009 | '

图片加载

\n' + 2010 | '

图片是项目中最常用的静态资源之一,本身包括了非常多的格式,诸如 png、jpeg、webp、avif、gif,当然,也包括经常用作图标的 svg 格式。这一部分我们主要讨论的是如何加载图片,也就是说怎么让图片在页面中正常显示

\n' + 2011 | '

1. 使用场景

\n' + 2012 | '

在日常的项目开发过程中,我们一般会遇到三种加载图片的场景:

\n' + 2013 | '
    \n' + 2014 | '
  1. 在 HTML 或者 JSX 中,通过 img 标签来加载图片,如:
  2. \n' + 2015 | '
\n' + 2016 | '
<img src="../../assets/a.png"></img>\n' +
2017 |           '
\n' + 2018 | '
    \n' + 2019 | '
  1. 在 CSS 中通过 background 属性加载图片,如:
  2. \n' + 2020 | '
\n' + 2021 | `
background: url('../../assets/b.png') norepeat;\n` +
2022 |           '
\n' + 2023 | '
    \n' + 2024 | '
  1. 在 JavaScript 中,通过脚本的方式动态指定图片的src属性,如:
  2. \n' + 2025 | '
\n' + 2026 | `
document.getElementById('hero-img').src = '../../assets/c.png'\n` +
2027 |           '
\n' + 2028 | '

当然,大家一般还会有别名路径的需求,比如地址前缀直接换成@assets,这样就不用开发人员手动寻址,降低开发时的心智负担。

\n' + 2029 | '

2. 在 Vite 中使用

\n' + 2030 | '

接下来让我们在目前的脚手架项目来进行实际的编码,你可以在 Vite 的配置文件中配置一下别名,方便后续的图片引入:

\n' + 2031 | '
// vite.config.ts\n' +
2032 |           `import path from 'path';\n` +
2033 |           '\n' +
2034 |           '{\n' +
2035 |           '  resolve: {\n' +
2036 |           '    // 别名配置\n' +
2037 |           '    alias: {\n' +
2038 |           `      '@assets': path.join(__dirname, 'src/assets')\n` +
2039 |           '    }\n' +
2040 |           '  }\n' +
2041 |           '}\n' +
2042 |           '
\n' + 2043 | '

这样 Vite 在遇到@assets路径的时候,会自动帮我们定位至根目录下的src/assets目录。值得注意的是,alias 别名配置不仅在 JavaScript 的 import 语句中生效,在 CSS 代码的 @importurl导入语句中也同样生效。

\n' + 2044 | '

现在 src/assets 目录的内容如下:

\n' + 2045 | '
.\n' +
2046 |           '├── icons\n' +
2047 |           '│   ├── favicon.svg\n' +
2048 |           '│   ├── logo-1.svg\n' +
2049 |           '│   ├── logo-2.svg\n' +
2050 |           '│   ├── logo-3.svg\n' +
2051 |           '│   ├── logo-4.svg\n' +
2052 |           '│   ├── logo-5.svg\n' +
2053 |           '│   └── logo.svg\n' +
2054 |           '└── imgs\n' +
2055 |           '    ├── background.png\n' +
2056 |           '    └── vite.png\n' +
2057 |           '
\n' + 2058 | '

接下来我们在 Header 组件中引入 vite.png这张图片:

\n' + 2059 | '
// Header/index.tsx\n' +
2060 |           `import React, { useEffect } from 'react';\n` +
2061 |           `import { devDependencies } from '../../../package.json';\n` +
2062 |           `import styles from './index.module.scss';\n` +
2063 |           '// 1. 导入图片\n' +
2064 |           `import logoSrc from '@assets/imgs/vite.png';\n` +
2065 |           '\n' +
2066 |           '// 方式一\n' +
2067 |           'export function Header() {\n' +
2068 |           '  return (\n' +
2069 |           '    <div className={`p-20px text-center ${styles.header}`}>\n' +
2070 |           '      <!-- 省略前面的组件内容 -->\n' +
2071 |           '      <!-- 使用图片 -->\n' +
2072 |           '      <img className="m-auto mb-4" src={logoSrc} alt="" />\n' +
2073 |           '    </div>\n' +
2074 |           '  );\n' +
2075 |           '}\n' +
2076 |           '\n' +
2077 |           '// 方式二\n' +
2078 |           'export function Header() {\n' +
2079 |           '  useEffect(() => {\n' +
2080 |           `    const img = document.getElementById('logo') as HTMLImageElement;\n` +
2081 |           '    img.src = logoSrc;\n' +
2082 |           '  }, []);\n' +
2083 |           '  return (\n' +
2084 |           '    <div className={`p-20px text-center ${styles.header}`}>\n' +
2085 |           '      <!-- 省略前面的组件内容 -->\n' +
2086 |           '      <!-- 使用图片 -->\n' +
2087 |           '      <img id="logo" className="m-auto mb-4" alt="" />\n' +
2088 |           '    </div>\n' +
2089 |           '  );\n' +
2090 |           '}\n' +
2091 |           '
\n' + 2092 | '

可以发现图片能够正常显示:

\n' + 2093 | '

image.png

\n' + 2094 | '

而图片路径也被解析为了正确的格式(/表示项目根路径):

\n' + 2095 | '

image.png

\n' + 2096 | '

OK,现在让我们进入 Header 组件的样式文件中添加background属性:

\n' + 2097 | '
.header {\n' +
2098 |           '  // 前面的样式代码省略\n' +
2099 |           `  background: url('@assets/imgs/background.png') no-repeat;\n` +
2100 |           '}\n' +
2101 |           '
\n' + 2102 | '

再次回到浏览器,可以看到生效后的背景如下:

\n' + 2103 | '

image.png

\n' + 2104 | '

3. SVG 组件方式加载

\n' + 2105 | '

刚才我们成功地在 Vite 中实现了图片的加载,上述这些加载的方式对于 svg 格式来说依然是适用的。不过,我们通常也希望能将 svg 当做一个组件来引入,这样我们可以很方便地修改 svg 的各种属性,而且比 img 标签的引入方式更加优雅。

\n' + 2106 | '

SVG 组件加载在不同的前端框架中的实现不太相同,社区中也已经了有了对应的插件支持:

\n' + 2107 | '\n' + 2112 | '

现在让我们在 React 脚手架项目中安装对应的依赖:

\n' + 2113 | '
pnpm i vite-plugin-svgr -D\n' +
2114 |           '
\n' + 2115 | '

然后需要在 vite 配置文件添加这个插件:

\n' + 2116 | '
// vite.config.ts\n' +
2117 |           `import svgr from 'vite-plugin-svgr';\n` +
2118 |           '\n' +
2119 |           '{\n' +
2120 |           '  plugins: [\n' +
2121 |           '    // 其它插件省略\n' +
2122 |           '    svgr()\n' +
2123 |           '  ]\n' +
2124 |           '}\n' +
2125 |           '
\n' + 2126 | '

随后注意要在 tsconfig.json 添加如下配置,否则会有类型错误:

\n' + 2127 | '
{\n' +
2128 |           '  "compilerOptions": {\n' +
2129 |           '    // 省略其它配置\n' +
2130 |           '    "types": ["vite-plugin-svgr/client"]\n' +
2131 |           '  }\n' +
2132 |           '}\n' +
2133 |           '
\n' + 2134 | '

接下来让我们在项目中使用 svg 组件:

\n' + 2135 | `
import { ReactComponent as ReactLogo } from '@assets/icons/logo.svg';\n` +
2136 |           '\n' +
2137 |           'export function Header() {\n' +
2138 |           '  return (\n' +
2139 |           '    // 其他组件内容省略\n' +
2140 |           '     <ReactLogo />\n' +
2141 |           '  )\n' +
2142 |           '}\n' +
2143 |           '
\n' + 2144 | '

回到浏览器中,你可以看到 svg 已经成功渲染:

\n' + 2145 | '

image.png

\n' + 2146 | '

JSON 加载

\n' + 2147 | '

Vite 中已经内置了对于 JSON 文件的解析,底层使用@rollup/pluginutilsdataToEsm 方法将 JSON 对象转换为一个包含各种具名导出的 ES 模块,使用姿势如下:

\n' + 2148 | `
import { version } from '../../../package.json';\n` +
2149 |           '
\n' + 2150 | '

不过你也可以在配置文件禁用按名导入的方式:

\n' + 2151 | '
// vite.config.ts\n' +
2152 |           '\n' +
2153 |           '{\n' +
2154 |           '  json: {\n' +
2155 |           '    stringify: true\n' +
2156 |           '  }\n' +
2157 |           '}\n' +
2158 |           '
\n' + 2159 | '

这样会将 JSON 的内容解析为export default JSON.parse("xxx"),这样会失去按名导出的能力,不过在 JSON 数据量比较大的时候,可以优化解析性能。

\n' + 2160 | '\n' + 2177 | '```\n' + 2178 | '\n' + 2179 | '2. 在 CSS 中通过 background 属性加载图片,如:\n' + 2180 | '```css\n' + 2181 | "background: url('../../assets/b.png') norepeat;\n" + 2182 | '```\n' + 2183 | '\n' + 2184 | '3. 在 JavaScript 中,通过脚本的方式动态指定图片的`src`属性,如:\n' + 2185 | '```ts\n' + 2186 | "document.getElementById('hero-img').src = '../../assets/c.png'\n" + 2187 | '```\n' + 2188 | '当然,大家一般还会有别名路径的需求,比如地址前缀直接换成`@assets`,这样就不用开发人员手动寻址,降低开发时的心智负担。\n' + 2189 | '\n' + 2190 | '### 2. 在 Vite 中使用\n' + 2191 | '接下来让我们在目前的脚手架项目来进行实际的编码,你可以在 Vite 的配置文件中配置一下别名,方便后续的图片引入:\n' + 2192 | '```ts\n' + 2193 | '// vite.config.ts\n' + 2194 | "import path from 'path';\n" + 2195 | '\n' + 2196 | '{\n' + 2197 | ' resolve: {\n' + 2198 | ' // 别名配置\n' + 2199 | ' alias: {\n' + 2200 | " '@assets': path.join(__dirname, 'src/assets')\n" + 2201 | ' }\n' + 2202 | ' }\n' + 2203 | '}\n' + 2204 | '```\n' + 2205 | '这样 Vite 在遇到`@assets`路径的时候,会自动帮我们定位至根目录下的`src/assets`目录。值得注意的是,alias 别名配置不仅在 JavaScript 的 import 语句中生效,在 CSS 代码的 `@import` 和 `url`导入语句中也同样生效。\n' + 2206 | '\n' + 2207 | '现在 `src/assets` 目录的内容如下:\n' + 2208 | '```bash\n' + 2209 | '.\n' + 2210 | '├── icons\n' + 2211 | '│ ├── favicon.svg\n' + 2212 | '│ ├── logo-1.svg\n' + 2213 | '│ ├── logo-2.svg\n' + 2214 | '│ ├── logo-3.svg\n' + 2215 | '│ ├── logo-4.svg\n' + 2216 | '│ ├── logo-5.svg\n' + 2217 | '│ └── logo.svg\n' + 2218 | '└── imgs\n' + 2219 | ' ├── background.png\n' + 2220 | ' └── vite.png\n' + 2221 | '```\n' + 2222 | '\n' + 2223 | '接下来我们在 Header 组件中引入 `vite.png`这张图片:\n' + 2224 | '```ts\n' + 2225 | '// Header/index.tsx\n' + 2226 | "import React, { useEffect } from 'react';\n" + 2227 | "import { devDependencies } from '../../../package.json';\n" + 2228 | "import styles from './index.module.scss';\n" + 2229 | '// 1. 导入图片\n' + 2230 | "import logoSrc from '@assets/imgs/vite.png';\n" + 2231 | '\n' + 2232 | '// 方式一\n' + 2233 | 'export function Header() {\n' + 2234 | ' return (\n' + 2235 | '
\n' + 2236 | ' \n' + 2237 | ' \n' + 2238 | ' \n' + 2239 | '
\n' + 2240 | ' );\n' + 2241 | '}\n' + 2242 | '\n' + 2243 | '// 方式二\n' + 2244 | 'export function Header() {\n' + 2245 | ' useEffect(() => {\n' + 2246 | " const img = document.getElementById('logo') as HTMLImageElement;\n" + 2247 | ' img.src = logoSrc;\n' + 2248 | ' }, []);\n' + 2249 | ' return (\n' + 2250 | '
\n' + 2251 | ' \n' + 2252 | ' \n' + 2253 | ' \n' + 2254 | '
\n' + 2255 | ' );\n' + 2256 | '}\n' + 2257 | '```\n' + 2258 | '\n' + 2259 | '可以发现图片能够正常显示:\n' + 2260 | '\n' + 2261 | '![image.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/66e4e71a894d4cb4ab8b6e5faca557a9~tplv-k3u1fbpfcp-watermark.image?)\n' + 2262 | '\n' + 2263 | '而图片路径也被解析为了正确的格式(`/`表示项目根路径):\n' + 2264 | '\n' + 2265 | '![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e338e4912cf04f03a796a16dd6f3268c~tplv-k3u1fbpfcp-watermark.image?)\n' + 2266 | '\n' + 2267 | 'OK,现在让我们进入 Header 组件的样式文件中添加`background`属性:\n' + 2268 | '```scss\n' + 2269 | '.header {\n' + 2270 | ' // 前面的样式代码省略\n' + 2271 | " background: url('@assets/imgs/background.png') no-repeat;\n" + 2272 | '}\n' + 2273 | '```\n' + 2274 | '再次回到浏览器,可以看到生效后的背景如下:\n' + 2275 | '\n' + 2276 | '![image.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/dac9bddf83e345fc8369e6ae821d83f2~tplv-k3u1fbpfcp-watermark.image?)\n' + 2277 | '\n' + 2278 | '### 3. SVG 组件方式加载\n' + 2279 | '刚才我们成功地在 Vite 中实现了图片的加载,上述这些加载的方式对于 svg 格式来说依然是适用的。不过,我们通常也希望能将 svg 当做一个组件来引入,这样我们可以很方便地修改 svg 的各种属性,而且比 img 标签的引入方式更加优雅。\n' + 2280 | '\n' + 2281 | 'SVG 组件加载在不同的前端框架中的实现不太相同,社区中也已经了有了对应的插件支持:\n' + 2282 | '\n' + 2283 | '- Vue2 项目中可以使用 [vite-plugin-vue2-svg](https://github.com/pakholeung37/vite-plugin-vue2-svg)插件。\n' + 2284 | '- Vue3 项目中可以引入 [vite-svg-loader](https://github.com/jpkleemans/vite-svg-loader)。\n' + 2285 | '- React 项目使用 [vite-plugin-svgr](https://github.com/pd4d10/vite-plugin-svgr)插件。\n' + 2286 | '\n' + 2287 | '现在让我们在 React 脚手架项目中安装对应的依赖:\n' + 2288 | '```ts\n' + 2289 | 'pnpm i vite-plugin-svgr -D\n' + 2290 | '```\n' + 2291 | '然后需要在 vite 配置文件添加这个插件:\n' + 2292 | '```ts\n' + 2293 | '// vite.config.ts\n' + 2294 | "import svgr from 'vite-plugin-svgr';\n" + 2295 | '\n' + 2296 | '{\n' + 2297 | ' plugins: [\n' + 2298 | ' // 其它插件省略\n' + 2299 | ' svgr()\n' + 2300 | ' ]\n' + 2301 | '}\n' + 2302 | '```\n' + 2303 | '随后注意要在 `tsconfig.json` 添加如下配置,否则会有类型错误:\n' + 2304 | '```json\n' + 2305 | '{\n' + 2306 | ' "compilerOptions": {\n' + 2307 | ' // 省略其它配置\n' + 2308 | ' "types": ["vite-plugin-svgr/client"]\n' + 2309 | ' }\n' + 2310 | '}\n' + 2311 | '```\n' + 2312 | '接下来让我们在项目中使用 svg 组件:\n' + 2313 | '```ts\n' + 2314 | "import { ReactComponent as ReactLogo } from '@assets/icons/logo.svg';\n" + 2315 | '\n' + 2316 | 'export function Header() {\n' + 2317 | ' return (\n' + 2318 | ' // 其他组件内容省略\n' + 2319 | ' \n' + 2320 | ' )\n' + 2321 | '}\n' + 2322 | '```\n' + 2323 | '回到浏览器中,你可以看到 svg 已经成功渲染:\n' + 2324 | '\n' + 2325 | '![image.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/795c22c495e74aebb7deea37e36e48ea~tplv-k3u1fbpfcp-watermark.image?)\n' + 2326 | '\n' + 2327 | '## JSON 加载\n' + 2328 | '\n' + 2329 | 'Vite 中已经内置了对于 JSON 文件的解析,底层使用`@rollup/pluginutils` 的 `dataToEsm` 方法将 JSON 对象转换为一个包含各种具名导出的 ES 模块,使用姿势如下:\n' + 2330 | '```ts\n' + 2331 | "import { version } from '../../../package.json';\n" + 2332 | '```\n' + 2333 | '\n' + 2334 | '不过你也可以在配置文件禁用按名导入的方式:\n' + 2335 | '```ts\n' + 2336 | '// vite.config.ts\n' + 2337 | '\n' + 2338 | '{\n' + 2339 | ' json: {\n' + 2340 | ' stringify: true\n' + 2341 | ' }\n' + 2342 | '}\n' + 2343 | '```\n' + 2344 | '这样会将 JSON 的内容解析为`export default JSON.parse("xxx")`,这样会失去`按名导出`的能力,不过在 JSON 数据量比较大的时候,可以优化解析性能。\n' + 2345 | '\n' + 2346 | '## Web Worker 脚本\n' + 2347 | 'Vite 中使用 Web Worker 也非常简单,我们可以在新建`Header/example.js`文件:\n' + 2348 | '```js\n' + 2349 | 'const start = () => {\n' + 2350 | ' let count = 0;\n' + 2351 | ' setInterval(() => {\n' + 2352 | ' // 给主线程传值\n' + 2353 | ' postMessage(++count);\n' + 2354 | ' }, 2000);\n' + 2355 | '};\n' + 2356 | '\n' + 2357 | 'start();\n' + 2358 | '```\n' + 2359 | '\n' + 2360 | '然后在 Header 组件中引入,引入的时候注意加上`?worker`后缀,相当于告诉 Vite 这是一个 Web Worker 脚本文件:\n' + 2361 | '\n' + 2362 | '```ts\n' + 2363 | "import Worker from './example.js?worker';\n" + 2364 | '// 1. 初始化 Worker 实例\n' + 2365 | 'const worker = new Worker();\n' + 2366 | '// 2. 主线程监听 worker 的信息\n' + 2367 | "worker.addEventListener('message', (e) => {\n" + 2368 | ' console.log(e);\n' + 2369 | '});\n' + 2370 | '```\n' + 2371 | '打开浏览器的控制面板,你可以看到 Worker 传给主线程的信息已经成功打印:\n' + 2372 | '\n' + 2373 | '![image.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5fe5b95939c74d8cba5a43826eab31c9~tplv-k3u1fbpfcp-watermark.image?)\n' + 2374 | '\n' + 2375 | '说明 Web Worker 脚本已经成功执行,也能与主线程正常通信。\n' + 2376 | '\n' + 2377 | '### Web Assembly 文件\n' + 2378 | 'Vite 对于 `.wasm` 文件也提供了开箱即用的支持,我们拿一个斐波拉契的 `.wasm` 文件(原文件已经放到[Github 仓库](https://github.com/sanyuan0704/juejin-book-vite/tree/main/4~7-vite-project-framework/src/components/Header)中)来进行一下实际操作,对应的 JavaScript 原文件如下:\n' + 2379 | '\n' + 2380 | '```ts\n' + 2381 | 'export function fib(n) {\n' + 2382 | ' var a = 0,\n' + 2383 | ' b = 1;\n' + 2384 | ' if (n > 0) {\n' + 2385 | ' while (--n) {\n' + 2386 | ' let t = a + b;\n' + 2387 | ' a = b;\n' + 2388 | ' b = t;\n' + 2389 | ' }\n' + 2390 | ' return b;\n' + 2391 | ' }\n' + 2392 | ' return a;\n' + 2393 | '}\n' + 2394 | '```\n' + 2395 | '让我们在组件中导入`fib.wasm`文件:\n' + 2396 | '```ts\n' + 2397 | '// Header/index.tsx\n' + 2398 | "import init from './fib.wasm';\n" + 2399 | '\n' + 2400 | 'type FibFunc = (num: number) => number;\n' + 2401 | '\n' + 2402 | 'init({}).then((exports) => {\n' + 2403 | ' const fibFunc = exports.fib as FibFunc;\n' + 2404 | " console.log('Fib result:', fibFunc(10));\n" + 2405 | '});\n' + 2406 | '```\n' + 2407 | 'Vite 会对`.wasm`文件的内容进行封装,默认导出为 init 函数,这个函数返回一个 Promise,因此我们可以在其 then 方法中拿到其导出的成员——`fib`方法。\n' + 2408 | '\n' + 2409 | '回到浏览器,我们可以查看到计算结果,说明 .wasm 文件已经被成功执行:\n' + 2410 | '\n' + 2411 | '![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7478ef95b7a847fca740218262b411cd~tplv-k3u1fbpfcp-watermark.image?)\n' + 2412 | '\n' + 2413 | '### 其它静态资源\n' + 2414 | '\n' + 2415 | '除了上述的一些资源格式,Vite 也对下面几类格式提供了内置的支持:\n' + 2416 | '\n' + 2417 | '- 媒体类文件,包括`mp4`、`webm`、`ogg`、`mp3`、`wav`、`flac`和`aac`。\n' + 2418 | '- 字体类文件。包括`woff`、`woff2`、`eot`、`ttf` 和 `otf`。\n' + 2419 | '- 文本类。包括`webmanifest`、`pdf`和`txt`。\n' + 2420 | '\n' + 2421 | '也就是说,你可以在 Vite 将这些类型的文件当做一个 ES 模块来导入使用。如果你的项目中还存在其它格式的静态资源,你可以通过`assetsInclude`配置让 Vite 来支持加载:\n' + 2422 | '```ts\n' + 2423 | '// vite.config.ts\n' + 2424 | '\n' + 2425 | '{\n' + 2426 | " assetsInclude: ['.gltf']\n" + 2427 | '}\n' + 2428 | '```\n' + 2429 | '\n' + 2430 | '### 特殊资源后缀\n' + 2431 | 'Vite 中引入静态资源时,也支持在路径最后加上一些特殊的 query 后缀,包括:\n' + 2432 | '- `?url`: 表示获取资源的路径,这在只想获取文件路径而不是内容的场景将会很有用。\n' + 2433 | '- `?raw`: 表示获取资源的字符串内容,如果你只想拿到资源的原始内容,可以使用这个后缀。\n' + 2434 | '- `?inline`: 表示资源强制内联,而不是打包成单独的文件。\n' + 2435 | '\n' + 2436 | '\n' + 2437 | '\n' + 2438 | '## 生产环境处理\n' + 2439 | '在前面的内容中,我们围绕着如何加载静态资源这个问题,在 Vite 中进行具体的编码实践,相信对于 Vite 中各种静态资源的使用你已经比较熟悉了。但另一方面,在生产环境下,我们又面临着一些新的问题。\n' + 2440 | '\n' + 2441 | '- 部署域名怎么配置?\n' + 2442 | '- 资源打包成单文件还是作为 Base64 格式内联?\n' + 2443 | '- 图片太大了怎么压缩?\n' + 2444 | '- svg 请求数量太多了怎么优化?\n' + 2445 | '\n' + 2446 | '### 1. 自定义部署域名\n' + 2447 | '\n' + 2448 | '一般在我们访问线上的站点时,站点里面一些静态资源的地址都包含了相应域名的前缀,如:\n' + 2449 | '```html\n' + 2450 | '\n' + 2451 | '```\n' + 2452 | '以上面这个地址例子,`https://sanyuan.cos.ap-beijing.myqcloud.com`是 CDN 地址前缀,`/logo.png`则是我们开发阶段使用的路径。那么,我们是不是需要在上线前把图片先上传到 CDN,然后将代码中的地址手动替换成线上地址呢?这样就太麻烦了!\n' + 2453 | '\n' + 2454 | '在 Vite 中我们可以有更加自动化的方式来实现地址的替换,只需要在配置文件中指定`base`参数即可:\n' + 2455 | '```ts\n' + 2456 | '// vite.config.ts\n' + 2457 | '// 是否为生产环境,在生产环境一般会注入 NODE_ENV 这个环境变量,见下面的环境变量文件配置\n' + 2458 | "const isProduction = process.env.NODE_ENV === 'production';\n" + 2459 | '// 填入项目的 CDN 域名地址\n' + 2460 | "const CDN_URL = 'xxxxxx';\n" + 2461 | '\n' + 2462 | '// 具体配置\n' + 2463 | '{\n' + 2464 | " base: isProduction ? CDN_URL: '/'\n" + 2465 | '}\n' + 2466 | '\n' + 2467 | '// .env.development\n' + 2468 | 'NODE_ENV=development\n' + 2469 | '\n' + 2470 | '// .env.production\n' + 2471 | 'NODE_ENV=production\n' + 2472 | '```\n' + 2473 | '注意在项目根目录新增的两个环境变量文件`.env.development`和`.env.production`,顾名思义,即分别在开发环境和生产环境注入一些环境变量,这里为了区分不同环境我们加上了`NODE_ENV`,你也可以根据需要添加别的环境变量。\n' + 2474 | '> 打包的时候 Vite 会自动将这些环境变量替换为相应的字符串。\n' + 2475 | '\n' + 2476 | '接着执行`pnpm run build`,可以发现产物中的静态资源地址已经自动加上了 CDN 地址前缀:\n' + 2477 | '\n' + 2478 | '![image.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2d910210d0b5484d8ebc4407b732b140~tplv-k3u1fbpfcp-watermark.image?)\n' + 2479 | '\n' + 2480 | '当然,HTML 中的一些 JS、CSS 资源链接也一起加上了 CDN 地址前缀:\n' + 2481 | '\n' + 2482 | '![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/15adeface82d4320b330b440c049f3ab~tplv-k3u1fbpfcp-watermark.image?)\n' + 2483 | '\n' + 2484 | '当然,有时候可能项目中的某些图片需要存放到另外的存储服务,一种直接的方案是将完整地址写死到 src 属性中,如:\n' + 2485 | '```ts\n' + 2486 | '\n' + 2487 | '```\n' + 2488 | '这样做显然是不太优雅的,我们可以通过定义环境变量的方式来解决这个问题,在项目根目录新增`.env`文件:\n' + 2489 | '```ts\n' + 2490 | '// 开发环境优先级: .env.development > .env\n' + 2491 | '// 生产环境优先级: .env.production > .env\n' + 2492 | '// .env 文件\n' + 2493 | 'VITE_IMG_BASE_URL=https://my-image-cdn.com\n' + 2494 | '```\n' + 2495 | '然后进入 `src/vite-env.d.ts`增加类型声明:\n' + 2496 | '```ts\n' + 2497 | '/// \n' + 2498 | '\n' + 2499 | 'interface ImportMetaEnv {\n' + 2500 | ' readonly VITE_APP_TITLE: string;\n' + 2501 | ' // 自定义的环境变量\n' + 2502 | ' readonly VITE_IMG_BASE_URL: string;\n' + 2503 | '}\n' + 2504 | '\n' + 2505 | 'interface ImportMeta {\n' + 2506 | ' readonly env: ImportMetaEnv;\n' + 2507 | '}\n' + 2508 | '```\n' + 2509 | '值得注意的是,如果某个环境变量要在 Vite 中通过 `import.meta.env` 访问,那么它必须以`VITE_`开头,如`VITE_IMG_BASE_URL`。接下来我们在组件中来使用这个环境变量:\n' + 2510 | '```html\n' + 2511 | "\n" + 2512 | '```\n' + 2513 | '接下来在`开发环境`启动项目或者`生产环境`打包后可以看到环境变量已经被替换,地址能够正常显示:\n' + 2514 | '\n' + 2515 | '\n' + 2516 | '![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/40484ed4df404b87b40b104cf2b9fdee~tplv-k3u1fbpfcp-watermark.image?)\n' + 2517 | '\n' + 2518 | '\n' + 2519 | '![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b6c4637475bb49818c9c5dd09001bb66~tplv-k3u1fbpfcp-watermark.image?)\n' + 2520 | '\n' + 2521 | '至此,我们就彻底解决了图片资源生产环境域名替换的问题。\n' + 2522 | '\n' + 2523 | '### 2. 单文件 or 内联?\n' + 2524 | '\n' + 2525 | '在 Vite 中,所有的静态资源都有两种构建方式,一种是打包成一个单文件,另一种是通过 base64 编码的格式内嵌到代码中。\n' + 2526 | '\n' + 2527 | '这两种方案到底应该如何来选择呢?\n' + 2528 | '\n' + 2529 | '对于比较小的资源,适合内联到代码中,一方面对`代码体积`的影响很小,另一方面可以减少不必要的网络请求,`优化网络性能`。而对于比较大的资源,就推荐单独打包成一个文件,而不是内联了,否则可能导致上 MB 的 base64 字符串内嵌到代码中,导致代码体积瞬间庞大,页面加载性能直线下降。\n' + 2530 | '\n' + 2531 | '![image.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0fb28454267d473aa02e8e0e486f2e9e~tplv-k3u1fbpfcp-watermark.image?)\n' + 2532 | '\n' + 2533 | 'Vite 中内置的优化方案是下面这样的:\n' + 2534 | '\n' + 2535 | '- 如果静态资源体积 >= 4KB,则提取成单独的文件\n' + 2536 | '- 如果静态资源体积 < 4KB,则作为 base64 格式的字符串内联\n' + 2537 | '\n' + 2538 | '上述的`4 KB`即为提取成单文件的临界值,当然,这个临界值你可以通过`build.assetsInlineLimit`自行配置,如下代码所示:\n' + 2539 | '```ts\n' + 2540 | '// vite.config.ts\n' + 2541 | '{\n' + 2542 | ' build: {\n' + 2543 | ' // 8 KB\n' + 2544 | ' assetsInlineLimit: 8 * 1024\n' + 2545 | ' }\n' + 2546 | '}\n' + 2547 | '```\n' + 2548 | '\n' + 2549 | '> svg 格式的文件不受这个临时值的影响,始终会打包成单独的文件,因为它和普通格式的图片不一样,需要动态设置一些属性\n' + 2550 | '\n' + 2551 | '### 3. 图片压缩\n' + 2552 | '图片资源的体积往往是项目产物体积的大头,如果能尽可能精简图片的体积,那么对项目整体打包产物体积的优化将会是非常明显的。在 JavaScript 领域有一个非常知名的图片压缩库[imagemin](https://www.npmjs.com/package/imagemin),作为一个底层的压缩工具,前端的项目中经常基于它来进行图片压缩,比如 Webpack 中大名鼎鼎的`image-webpack-loader`。社区当中也已经有了开箱即用的 Vite 插件——`vite-plugin-imagemin`,首先让我们来安装它:\n' + 2553 | '```ts\n' + 2554 | 'pnpm i vite-plugin-imagemin -D\n' + 2555 | '```\n' + 2556 | '随后在 Vite 配置文件中引入:'... 4476 more characters, 2557 | is_free: 1, 2558 | read_time: 2547, 2559 | read_count: 8681, 2560 | comment_count: 60, 2561 | ctime: 1643634078, 2562 | mtime: 1643634078, 2563 | is_update: 0, 2564 | draft_read_time: 2547, 2565 | vid: '', 2566 | reading_progress: null 2567 | } 2568 | } 2569 | }, 2570 | level: 'info', 2571 | label: 'juejin', 2572 | timestamp: '2022-09-22 08:01:11.366 AM' 2573 | } 2574 | { 2575 | message: { 2576 | err_no: 0, 2577 | err_msg: 'success', 2578 | data: { 2579 | lotteries: [ 2580 | { 2581 | user_id: '1609340755118711', 2582 | history_id: '7145682521303302152', 2583 | user_name: '陈辰宸', 2584 | user_avatar: 'https://p26-passport.byteacctimg.com/img/user-avatar/86d146585e7f36c9860888f96ef29f4e~300x300.image', 2585 | lottery_name: '字节咖啡保温杯', 2586 | lottery_image: 'https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fa31d47d93ce4793a50b664a3e032b18~tplv-k3u1fbpfcp-no-mark:0:0:0:0.image?', 2587 | date: 1663733861, 2588 | dip_lucky_user_count: 3258, 2589 | dip_lucky_users: [ 2590 | { 2591 | user_id: '3289337926283534', 2592 | user_name: 'Mingo-233', 2593 | avatar_large: 'https://p9-passport.byteacctimg.com/img/user-avatar/685ed9cfa7fa6a47b379080849415975~300x300.image' 2594 | }, 2595 | { 2596 | user_id: '75222061691005', 2597 | user_name: '用户7764100806', 2598 | avatar_large: 'https://p6-passport.byteacctimg.com/img/user-avatar/0e95e6f86227319d55afa613be7ba8a5~300x300.image' 2599 | }, 2600 | { 2601 | user_id: '254742430497751', 2602 | user_name: 'Yxx01', 2603 | avatar_large: 'https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/4/23/171a4b84cf4d96f9~tplv-t2oaga2asx-image.image' 2604 | }, 2605 | { 2606 | user_id: '831686056870728', 2607 | user_name: '星星7652', 2608 | avatar_large: 'https://p3-passport.byteacctimg.com/img/mosaic-legacy/3795/3047680722~300x300.image' 2609 | }, 2610 | { 2611 | user_id: '2133507821543879', 2612 | user_name: '用户8496562525284', 2613 | avatar_large: 'https://p9-passport.byteacctimg.com/img/mosaic-legacy/3793/3131589739~300x300.image' 2614 | }, 2615 | { 2616 | user_id: '532627197277133', 2617 | user_name: '指挥官乄', 2618 | avatar_large: 'https://p6-passport.byteacctimg.com/img/user-avatar/65f2953efc7779724440463d8d4eecb4~300x300.image' 2619 | }, 2620 | { 2621 | user_id: '3734356412862221', 2622 | user_name: '乌龙茶风', 2623 | avatar_large: 'https://p6-passport.byteacctimg.com/img/mosaic-legacy/3795/3033762272~300x300.image' 2624 | }, 2625 | { 2626 | user_id: '2260251638763998', 2627 | user_name: 'Breeze同学', 2628 | avatar_large: 'https://p9-passport.byteacctimg.com/img/user-avatar/293596ee5eec685bdc10550400a0a0ea~300x300.image' 2629 | }, 2630 | { 2631 | user_id: '286326221974173', 2632 | user_name: '用户7761966292', 2633 | avatar_large: 'https://p6-passport.byteacctimg.com/img/user-avatar/4074f1456f3507bceca40badb972308f~300x300.image' 2634 | }, 2635 | { 2636 | user_id: '3158247505146359', 2637 | user_name: 'Blame', 2638 | avatar_large: 'https://p26-passport.byteacctimg.com/img/user-avatar/8180ac6030b58031939ecac5d0067ffd~300x300.image' 2639 | } 2640 | ] 2641 | }, 2642 | { 2643 | user_id: '3545243881060551', 2644 | history_id: '7145378407000801288', 2645 | user_name: '啊该用户不存在', 2646 | user_avatar: 'https://p26-passport.byteacctimg.com/img/mosaic-legacy/3795/3033762272~300x300.image', 2647 | lottery_name: '字节咖啡保温杯', 2648 | lottery_image: 'https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fa31d47d93ce4793a50b664a3e032b18~tplv-k3u1fbpfcp-no-mark:0:0:0:0.image?', 2649 | date: 1663663108, 2650 | dip_lucky_user_count: 5850, 2651 | dip_lucky_users: [ 2652 | { 2653 | user_id: '1121923259444189', 2654 | user_name: '浪费西626', 2655 | avatar_large: 'https://p6-passport.byteacctimg.com/img/mosaic-legacy/3793/3131589739~300x300.image' 2656 | }, 2657 | { 2658 | user_id: '4248168662576446', 2659 | user_name: '摸鱼之', 2660 | avatar_large: 'https://p9-passport.byteacctimg.com/img/user-avatar/4c05eed314ec83b3bea3d8e6ee51e6fc~300x300.image' 2661 | }, 2662 | { 2663 | user_id: '1425402138994237', 2664 | user_name: 'AVOID_NPE', 2665 | avatar_large: 'https://p6-passport.byteacctimg.com/img/user-avatar/3265693a5cfcb3ae7ff8832f7652c977~300x300.image' 2666 | }, 2667 | { 2668 | user_id: '2876786657998744', 2669 | user_name: '王瑞281', 2670 | avatar_large: 'https://p3-passport.byteacctimg.com/img/user-avatar/f8d7d83ea3dc5db61584baf9b61779b0~300x300.image' 2671 | }, 2672 | { 2673 | user_id: '2137887890214120', 2674 | user_name: '清欢爱蹦跶', 2675 | avatar_large: 'https://p3-passport.byteacctimg.com/img/user-avatar/4e5dc0ffd7b8080e614151fe93e61e41~300x300.image' 2676 | }, 2677 | { 2678 | user_id: '128022378711965', 2679 | user_name: 'biubiubiu66', 2680 | avatar_large: 'https://p6-passport.byteacctimg.com/img/mosaic-legacy/3793/3114521287~300x300.image' 2681 | }, 2682 | { 2683 | user_id: '3746751243622951', 2684 | user_name: '膨胀男孩', 2685 | avatar_large: 'https://p26-passport.byteacctimg.com/img/user-avatar/3dd47e854dcc77a16fbaf9715887e3a5~300x300.image' 2686 | }, 2687 | { 2688 | user_id: '2867148445460718', 2689 | user_name: 'Wynne', 2690 | avatar_large: 'https://p9-passport.byteacctimg.com/img/user-avatar/d958acaffaf85f5849539a064ce46215~300x300.image' 2691 | }, 2692 | { 2693 | user_id: '2277843824803965', 2694 | user_name: '羽习习', 2695 | avatar_large: 'https://p6-passport.byteacctimg.com/img/user-avatar/ddcb06aacecded98a724ffe2ceabf92d~300x300.image' 2696 | }, 2697 | { 2698 | user_id: '1253923373721646', 2699 | user_name: '朱伟2943', 2700 | avatar_large: 'https://p9-passport.byteacctimg.com/img/user-avatar/1651e9d80184a86f0fa0061972875525~300x300.image' 2701 | } 2702 | ] 2703 | }, 2704 | { 2705 | user_id: '3421335916645806', 2706 | history_id: '7145036492027461667', 2707 | user_name: '喵呼', 2708 | user_avatar: 'https://p9-passport.byteacctimg.com/img/user-avatar/098e4fe103d08fa4a885d182ba332557~300x300.image', 2709 | lottery_name: '字节咖啡保温杯', 2710 | lottery_image: 'https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fa31d47d93ce4793a50b664a3e032b18~tplv-k3u1fbpfcp-no-mark:0:0:0:0.image?', 2711 | date: 1663583732, 2712 | dip_lucky_user_count: 6955, 2713 | dip_lucky_users: [ 2714 | { 2715 | user_id: '1429831466884327', 2716 | user_name: '君越', 2717 | avatar_large: 'https://p26-passport.byteacctimg.com/img/mosaic-legacy/3795/3044413937~300x300.image' 2718 | }, 2719 | { 2720 | user_id: '1183540868031021', 2721 | user_name: '瀚莎', 2722 | avatar_large: 'https://p6-passport.byteacctimg.com/img/user-avatar/42578f7778606d02639f76a344017459~300x300.image' 2723 | }, 2724 | { 2725 | user_id: '1676109687039799', 2726 | user_name: 'Sus', 2727 | avatar_large: 'https://p26-passport.byteacctimg.com/img/user-avatar/df477141e030eba72a944a983937475d~300x300.image' 2728 | }, 2729 | { 2730 | user_id: '3206620968128407', 2731 | user_name: '用户1892712826183', 2732 | avatar_large: 'https://p9-passport.byteacctimg.com/img/mosaic-legacy/3795/3044413937~300x300.image' 2733 | }, 2734 | { 2735 | user_id: '1046390799613095', 2736 | user_name: 'Jock', 2737 | avatar_large: 'https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2018/3/26/162600753d4df59d~tplv-t2oaga2asx-image.image' 2738 | }, 2739 | { 2740 | user_id: '2432573356910189', 2741 | user_name: 'BestSum', 2742 | avatar_large: 'https://p6-passport.byteacctimg.com/img/user-avatar/fc3f542dba72622688908b2787b86300~300x300.image' 2743 | }, 2744 | { 2745 | user_id: '1072769194014221', 2746 | user_name: '蛙蛙世界第一可爱', 2747 | avatar_large: 'https://p6-passport.byteacctimg.com/img/user-avatar/5a1736a080ed5a597867c31a762c5bb1~300x300.image' 2748 | }, 2749 | { 2750 | user_id: '1038354422468248', 2751 | user_name: '代码农民工', 2752 | avatar_large: 'https://p3-passport.byteacctimg.com/img/mosaic-legacy/3795/3047680722~300x300.image' 2753 | }, 2754 | { 2755 | user_id: '202725480475015', 2756 | user_name: 'IoI', 2757 | avatar_large: 'https://p26-passport.byteacctimg.com/img/user-avatar/dc456a446ed009a75e3efef2c1fba387~300x300.image' 2758 | }, 2759 | { 2760 | user_id: '3118651488607710', 2761 | user_name: '乐le', 2762 | avatar_large: 'https://p9-passport.byteacctimg.com/img/user-avatar/18d1afc4b11d4d489c48c57c3e954ad3~300x300.image' 2763 | } 2764 | ] 2765 | }, 2766 | { 2767 | user_id: '1415826707326701', 2768 | history_id: '7144895216900636680', 2769 | user_name: '穿过繁华', 2770 | user_avatar: 'https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2018/6/12/163f306aa3db6a24~tplv-t2oaga2asx-image.image', 2771 | lottery_name: '九阳养生电热水壶', 2772 | lottery_image: 'https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b4826808d48547fd966ceec758d8b7c5~tplv-k3u1fbpfcp-no-mark:0:0:0:0.image?', 2773 | date: 1663550559, 2774 | dip_lucky_user_count: 3770, 2775 | dip_lucky_users: [ 2776 | { 2777 | user_id: '3280598428290295', 2778 | user_name: '打工人在掘金', 2779 | avatar_large: 'https://p26-passport.byteacctimg.com/img/user-avatar/e0798b5c038fbd7db243ce0c35f2c670~300x300.image' 2780 | }, 2781 | { 2782 | user_id: '3122268756261864', 2783 | user_name: '阿星Plus', 2784 | avatar_large: 'https://p3-passport.byteacctimg.com/img/user-avatar/3cc33f54c52379926f4f24c4bc44ff4d~300x300.image' 2785 | }, 2786 | { 2787 | user_id: '1178283593967373', 2788 | user_name: 'SloppyJack', 2789 | avatar_large: 'https://p6-passport.byteacctimg.com/img/user-avatar/20d2a9586e883e32f0ec97bcf4a77221~300x300.image' 2790 | }, 2791 | { 2792 | user_id: '395479917012008', 2793 | user_name: '解柒', 2794 | avatar_large: 'https://p3-passport.byteacctimg.com/img/user-avatar/4148984546be5ae33fe190a7d192ba28~300x300.image' 2795 | }, 2796 | { 2797 | user_id: '110431258292296', 2798 | user_name: '胡春梅5419', 2799 | avatar_large: 'https://p3-passport.byteacctimg.com/img/user-avatar/b4eca1a03c949f30b113f464d307d95b~300x300.image' 2800 | }, 2801 | { 2802 | user_id: '676954894778008', 2803 | user_name: '小熊猫jk8899', 2804 | avatar_large: 'https://p3-passport.byteacctimg.com/img/user-avatar/a692705a85783ebd2c411a5017c5cec4~300x300.image' 2805 | }, 2806 | { 2807 | user_id: '518625217492520', 2808 | user_name: 'logic1486951766000', 2809 | avatar_large: 'https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/6/6/17289cfa48bd55b4~tplv-t2oaga2asx-image.image' 2810 | }, 2811 | { 2812 | user_id: '2208259628081037', 2813 | user_name: '老鸨', 2814 | avatar_large: 'https://p6-passport.byteacctimg.com/img/user-avatar/a02dd88706ad629629cedf59659e827b~300x300.image' 2815 | }, 2816 | { 2817 | user_id: '3553254511227815', 2818 | user_name: '淡若白纸', 2819 | avatar_large: 'https://p26-passport.byteacctimg.com/img/user-avatar/e45e8d28f1306b0e30c91cd152fb8bc6~300x300.image' 2820 | }, 2821 | { 2822 | user_id: '3734360393520632', 2823 | user_name: '白凤倚剑归', 2824 | avatar_large: 'https://p3-passport.byteacctimg.com/img/user-avatar/38ddfa52be921cd945b7cab68f743cca~300x300.image' 2825 | } 2826 | ] 2827 | }, 2828 | { 2829 | user_id: '1099167359570488', 2830 | history_id: '7144551731223855134', 2831 | user_name: '进击的李小明', 2832 | user_avatar: 'https://p3-passport.byteacctimg.com/img/user-avatar/4294d0e5535896632975d3253eb50c5f~300x300.image', 2833 | lottery_name: '九阳养生电热水壶', 2834 | lottery_image: 'https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b4826808d48547fd966ceec758d8b7c5~tplv-k3u1fbpfcp-no-mark:0:0:0:0.image?', 2835 | date: 1663470584, 2836 | dip_lucky_user_count: 5648, 2837 | dip_lucky_users: [ 2838 | { 2839 | user_id: '3307798078435901', 2840 | user_name: '云的犹豫', 2841 | avatar_large: 'https://p6-passport.byteacctimg.com/img/user-avatar/75de6d0f13ccf92d144a237375f63068~300x300.image' 2842 | }, 2843 | { 2844 | user_id: '629342049668248', 2845 | user_name: '勇敢酷244', 2846 | avatar_large: 'https://p3-passport.byteacctimg.com/img/mosaic-legacy/3795/3047680722~300x300.image' 2847 | }, 2848 | { 2849 | user_id: '3866304278959549', 2850 | user_name: '透过范262', 2851 | avatar_large: 'https://p6-passport.byteacctimg.com/img/mosaic-legacy/3793/3131589739~300x300.image' 2852 | }, 2853 | { 2854 | user_id: '589781337648110', 2855 | user_name: '用户3172193994629', 2856 | avatar_large: 'https://p9-passport.byteacctimg.com/img/mosaic-legacy/3791/5035712059~300x300.image' 2857 | }, 2858 | { 2859 | user_id: '3752014209624807', 2860 | user_name: '萧雪7947', 2861 | avatar_large: 'https://p26-passport.byteacctimg.com/img/user-avatar/19bdfc6d301af0a28ec82957c3160789~300x300.image' 2862 | }, 2863 | { 2864 | user_id: '157979791615623', 2865 | user_name: 'OnePiece', 2866 | avatar_large: 'https://p26-passport.byteacctimg.com/img/user-avatar/45ed7222fe6b933671f46edd16861d44~300x300.image' 2867 | }, 2868 | { 2869 | user_id: '1881969231146696', 2870 | user_name: 'pk_啾', 2871 | avatar_large: 'https://p3-passport.byteacctimg.com/img/user-avatar/2e5f2e3192a26f7922ed7e463ccc8e36~300x300.image' 2872 | }, 2873 | { 2874 | user_id: '518625217489949', 2875 | user_name: 'ADark0915', 2876 | avatar_large: 'https://p6-passport.byteacctimg.com/img/user-avatar/d12205fb129d718156799d267d085257~300x300.image' 2877 | }, 2878 | { 2879 | user_id: '2832815186774279', 2880 | user_name: 'cn0379_2', 2881 | avatar_large: 'https://p26-passport.byteacctimg.com/img/mosaic-legacy/3793/3131589739~300x300.image' 2882 | }, 2883 | { 2884 | user_id: '3966693684034861', 2885 | user_name: 'zhaishuo', 2886 | avatar_large: 'https://p6-passport.byteacctimg.com/img/user-avatar/2373d4e498009055e90f923bb0767e3b~300x300.image' 2887 | } 2888 | ] 2889 | } 2890 | ], 2891 | count: 1000 2892 | } 2893 | }, 2894 | level: 'info', 2895 | label: 'juejin', 2896 | timestamp: '2022-09-22 08:01:11.383 AM' 2897 | } 2898 | { 2899 | message: { 2900 | err_no: 0, 2901 | err_msg: 'success', 2902 | data: { dip_action: 1, has_dip: false, total_value: 40, dip_value: 10 } 2903 | }, 2904 | level: 'info', 2905 | label: 'juejin', 2906 | timestamp: '2022-09-22 08:01:11.575 AM' 2907 | } 2908 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "automatic-check-script", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "devDependencies": { 13 | "dotenv": "^16.0.2", 14 | "express": "^4.17.1", 15 | "node-schedule": "^2.1.0", 16 | "nodemailer": "^6.7.8", 17 | "winston": "^3.8.1", 18 | "winston-daily-rotate-file": "^4.7.1" 19 | }, 20 | "dependencies": { 21 | "axios": "^0.27.2", 22 | "cors": "^2.8.5", 23 | "redis": "^4.3.1" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /pm2.md: -------------------------------------------------------------------------------- 1 | 进入bin目录启动:pm2 start www / pm2 start app.js 2 | pm2 start app.js --name="fx67ll" 启动并命名为fx67ll,没有命名的话后续可以用id替代name 3 | pm2 start app.js --watch 当文件变化时自动重启应用 4 | pm2 start script.sh 启动bash脚本 5 | pm2 list 查看所有启动的应用列表 6 | pm2 monit 显示每个应用程序的CPU和内存占用情况 7 | pm2 show [app-id/app-name] 显示指定应用程序的所有信息 8 | pm2 log 显示应用程序的日志信息 9 | pm2 log [app-id/app-name] 显示指定应用程序的日志信息 10 | pm2 flush 清空所有日志文件 11 | pm2 stop all 停止所有应用程序 12 | pm2 stop [app-id/app-name] 停止指定应用程序 13 | pm2 restart all 重启所有应用程序 14 | pm2 restart [app-id/app-name] 重启指定应用程序 15 | pm2 delete all 关闭并删除所有应用程序 16 | pm2 delete [app-id/app-name] 删除指定的应用程序 17 | pm2 reset [app-id/app-name] 重置重启数量 18 | pm2 startup 创建开机自启动命令 19 | pm2 save 保存当前应用列表 20 | pm2 resurrect 重新加载保存的应用列表 21 | pm2 update 保存进程,杀死并重启进程,一般用于更新pm2版本 22 | pm2 ecosystem 生成一个示例json配置文件 23 | -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: 5.4 2 | 3 | specifiers: 4 | axios: ^0.27.2 5 | cors: ^2.8.5 6 | dotenv: ^16.0.2 7 | express: ^4.17.1 8 | node-schedule: ^2.1.0 9 | nodemailer: ^6.7.8 10 | redis: ^4.3.1 11 | winston: ^3.8.1 12 | winston-daily-rotate-file: ^4.7.1 13 | 14 | dependencies: 15 | axios: 0.27.2 16 | cors: 2.8.5 17 | redis: 4.3.1 18 | 19 | devDependencies: 20 | dotenv: 16.0.2 21 | express: 4.18.1 22 | node-schedule: 2.1.0 23 | nodemailer: 6.7.8 24 | winston: 3.8.1 25 | winston-daily-rotate-file: 4.7.1_winston@3.8.1 26 | 27 | packages: 28 | 29 | /@colors/colors/1.5.0: 30 | resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} 31 | engines: {node: '>=0.1.90'} 32 | dev: true 33 | 34 | /@dabh/diagnostics/2.0.3: 35 | resolution: {integrity: sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==} 36 | dependencies: 37 | colorspace: 1.1.4 38 | enabled: 2.0.0 39 | kuler: 2.0.0 40 | dev: true 41 | 42 | /@redis/bloom/1.0.2_@redis+client@1.3.0: 43 | resolution: {integrity: sha512-EBw7Ag1hPgFzdznK2PBblc1kdlj5B5Cw3XwI9/oG7tSn85/HKy3X9xHy/8tm/eNXJYHLXHJL/pkwBpFMVVefkw==} 44 | peerDependencies: 45 | '@redis/client': ^1.0.0 46 | dependencies: 47 | '@redis/client': 1.3.0 48 | dev: false 49 | 50 | /@redis/client/1.3.0: 51 | resolution: {integrity: sha512-XCFV60nloXAefDsPnYMjHGtvbtHR8fV5Om8cQ0JYqTNbWcQo/4AryzJ2luRj4blveWazRK/j40gES8M7Cp6cfQ==} 52 | engines: {node: '>=14'} 53 | dependencies: 54 | cluster-key-slot: 1.1.0 55 | generic-pool: 3.8.2 56 | yallist: 4.0.0 57 | dev: false 58 | 59 | /@redis/graph/1.0.1_@redis+client@1.3.0: 60 | resolution: {integrity: sha512-oDE4myMCJOCVKYMygEMWuriBgqlS5FqdWerikMoJxzmmTUErnTRRgmIDa2VcgytACZMFqpAOWDzops4DOlnkfQ==} 61 | peerDependencies: 62 | '@redis/client': ^1.0.0 63 | dependencies: 64 | '@redis/client': 1.3.0 65 | dev: false 66 | 67 | /@redis/json/1.0.4_@redis+client@1.3.0: 68 | resolution: {integrity: sha512-LUZE2Gdrhg0Rx7AN+cZkb1e6HjoSKaeeW8rYnt89Tly13GBI5eP4CwDVr+MY8BAYfCg4/N15OUrtLoona9uSgw==} 69 | peerDependencies: 70 | '@redis/client': ^1.0.0 71 | dependencies: 72 | '@redis/client': 1.3.0 73 | dev: false 74 | 75 | /@redis/search/1.1.0_@redis+client@1.3.0: 76 | resolution: {integrity: sha512-NyFZEVnxIJEybpy+YskjgOJRNsfTYqaPbK/Buv6W2kmFNaRk85JiqjJZA5QkRmWvGbyQYwoO5QfDi2wHskKrQQ==} 77 | peerDependencies: 78 | '@redis/client': ^1.0.0 79 | dependencies: 80 | '@redis/client': 1.3.0 81 | dev: false 82 | 83 | /@redis/time-series/1.0.3_@redis+client@1.3.0: 84 | resolution: {integrity: sha512-OFp0q4SGrTH0Mruf6oFsHGea58u8vS/iI5+NpYdicaM+7BgqBZH8FFvNZ8rYYLrUO/QRqMq72NpXmxLVNcdmjA==} 85 | peerDependencies: 86 | '@redis/client': ^1.0.0 87 | dependencies: 88 | '@redis/client': 1.3.0 89 | dev: false 90 | 91 | /accepts/1.3.8: 92 | resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} 93 | engines: {node: '>= 0.6'} 94 | dependencies: 95 | mime-types: 2.1.35 96 | negotiator: 0.6.3 97 | dev: true 98 | 99 | /array-flatten/1.1.1: 100 | resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} 101 | dev: true 102 | 103 | /async/3.2.4: 104 | resolution: {integrity: sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==} 105 | dev: true 106 | 107 | /asynckit/0.4.0: 108 | resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} 109 | dev: false 110 | 111 | /axios/0.27.2: 112 | resolution: {integrity: sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==} 113 | dependencies: 114 | follow-redirects: 1.15.1 115 | form-data: 4.0.0 116 | transitivePeerDependencies: 117 | - debug 118 | dev: false 119 | 120 | /body-parser/1.20.0: 121 | resolution: {integrity: sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==} 122 | engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} 123 | dependencies: 124 | bytes: 3.1.2 125 | content-type: 1.0.4 126 | debug: 2.6.9 127 | depd: 2.0.0 128 | destroy: 1.2.0 129 | http-errors: 2.0.0 130 | iconv-lite: 0.4.24 131 | on-finished: 2.4.1 132 | qs: 6.10.3 133 | raw-body: 2.5.1 134 | type-is: 1.6.18 135 | unpipe: 1.0.0 136 | transitivePeerDependencies: 137 | - supports-color 138 | dev: true 139 | 140 | /bytes/3.1.2: 141 | resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} 142 | engines: {node: '>= 0.8'} 143 | dev: true 144 | 145 | /call-bind/1.0.2: 146 | resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==} 147 | dependencies: 148 | function-bind: 1.1.1 149 | get-intrinsic: 1.1.2 150 | dev: true 151 | 152 | /cluster-key-slot/1.1.0: 153 | resolution: {integrity: sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw==} 154 | engines: {node: '>=0.10.0'} 155 | dev: false 156 | 157 | /color-convert/1.9.3: 158 | resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} 159 | dependencies: 160 | color-name: 1.1.3 161 | dev: true 162 | 163 | /color-name/1.1.3: 164 | resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} 165 | dev: true 166 | 167 | /color-name/1.1.4: 168 | resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} 169 | dev: true 170 | 171 | /color-string/1.9.1: 172 | resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} 173 | dependencies: 174 | color-name: 1.1.4 175 | simple-swizzle: 0.2.2 176 | dev: true 177 | 178 | /color/3.2.1: 179 | resolution: {integrity: sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==} 180 | dependencies: 181 | color-convert: 1.9.3 182 | color-string: 1.9.1 183 | dev: true 184 | 185 | /colorspace/1.1.4: 186 | resolution: {integrity: sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==} 187 | dependencies: 188 | color: 3.2.1 189 | text-hex: 1.0.0 190 | dev: true 191 | 192 | /combined-stream/1.0.8: 193 | resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} 194 | engines: {node: '>= 0.8'} 195 | dependencies: 196 | delayed-stream: 1.0.0 197 | dev: false 198 | 199 | /content-disposition/0.5.4: 200 | resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} 201 | engines: {node: '>= 0.6'} 202 | dependencies: 203 | safe-buffer: 5.2.1 204 | dev: true 205 | 206 | /content-type/1.0.4: 207 | resolution: {integrity: sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==} 208 | engines: {node: '>= 0.6'} 209 | dev: true 210 | 211 | /cookie-signature/1.0.6: 212 | resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} 213 | dev: true 214 | 215 | /cookie/0.5.0: 216 | resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} 217 | engines: {node: '>= 0.6'} 218 | dev: true 219 | 220 | /cors/2.8.5: 221 | resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} 222 | engines: {node: '>= 0.10'} 223 | dependencies: 224 | object-assign: 4.1.1 225 | vary: 1.1.2 226 | dev: false 227 | 228 | /cron-parser/3.5.0: 229 | resolution: {integrity: sha512-wyVZtbRs6qDfFd8ap457w3XVntdvqcwBGxBoTvJQH9KGVKL/fB+h2k3C8AqiVxvUQKN1Ps/Ns46CNViOpVDhfQ==} 230 | engines: {node: '>=0.8'} 231 | dependencies: 232 | is-nan: 1.3.2 233 | luxon: 1.28.0 234 | dev: true 235 | 236 | /debug/2.6.9: 237 | resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} 238 | peerDependencies: 239 | supports-color: '*' 240 | peerDependenciesMeta: 241 | supports-color: 242 | optional: true 243 | dependencies: 244 | ms: 2.0.0 245 | dev: true 246 | 247 | /define-properties/1.1.4: 248 | resolution: {integrity: sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==} 249 | engines: {node: '>= 0.4'} 250 | dependencies: 251 | has-property-descriptors: 1.0.0 252 | object-keys: 1.1.1 253 | dev: true 254 | 255 | /delayed-stream/1.0.0: 256 | resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} 257 | engines: {node: '>=0.4.0'} 258 | dev: false 259 | 260 | /depd/2.0.0: 261 | resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} 262 | engines: {node: '>= 0.8'} 263 | dev: true 264 | 265 | /destroy/1.2.0: 266 | resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} 267 | engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} 268 | dev: true 269 | 270 | /dotenv/16.0.2: 271 | resolution: {integrity: sha512-JvpYKUmzQhYoIFgK2MOnF3bciIZoItIIoryihy0rIA+H4Jy0FmgyKYAHCTN98P5ybGSJcIFbh6QKeJdtZd1qhA==} 272 | engines: {node: '>=12'} 273 | dev: true 274 | 275 | /ee-first/1.1.1: 276 | resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} 277 | dev: true 278 | 279 | /enabled/2.0.0: 280 | resolution: {integrity: sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==} 281 | dev: true 282 | 283 | /encodeurl/1.0.2: 284 | resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} 285 | engines: {node: '>= 0.8'} 286 | dev: true 287 | 288 | /escape-html/1.0.3: 289 | resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} 290 | dev: true 291 | 292 | /etag/1.8.1: 293 | resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} 294 | engines: {node: '>= 0.6'} 295 | dev: true 296 | 297 | /express/4.18.1: 298 | resolution: {integrity: sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q==} 299 | engines: {node: '>= 0.10.0'} 300 | dependencies: 301 | accepts: 1.3.8 302 | array-flatten: 1.1.1 303 | body-parser: 1.20.0 304 | content-disposition: 0.5.4 305 | content-type: 1.0.4 306 | cookie: 0.5.0 307 | cookie-signature: 1.0.6 308 | debug: 2.6.9 309 | depd: 2.0.0 310 | encodeurl: 1.0.2 311 | escape-html: 1.0.3 312 | etag: 1.8.1 313 | finalhandler: 1.2.0 314 | fresh: 0.5.2 315 | http-errors: 2.0.0 316 | merge-descriptors: 1.0.1 317 | methods: 1.1.2 318 | on-finished: 2.4.1 319 | parseurl: 1.3.3 320 | path-to-regexp: 0.1.7 321 | proxy-addr: 2.0.7 322 | qs: 6.10.3 323 | range-parser: 1.2.1 324 | safe-buffer: 5.2.1 325 | send: 0.18.0 326 | serve-static: 1.15.0 327 | setprototypeof: 1.2.0 328 | statuses: 2.0.1 329 | type-is: 1.6.18 330 | utils-merge: 1.0.1 331 | vary: 1.1.2 332 | transitivePeerDependencies: 333 | - supports-color 334 | dev: true 335 | 336 | /fecha/4.2.3: 337 | resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==} 338 | dev: true 339 | 340 | /file-stream-rotator/0.6.1: 341 | resolution: {integrity: sha512-u+dBid4PvZw17PmDeRcNOtCP9CCK/9lRN2w+r1xIS7yOL9JFrIBKTvrYsxT4P0pGtThYTn++QS5ChHaUov3+zQ==} 342 | dependencies: 343 | moment: 2.29.4 344 | dev: true 345 | 346 | /finalhandler/1.2.0: 347 | resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==} 348 | engines: {node: '>= 0.8'} 349 | dependencies: 350 | debug: 2.6.9 351 | encodeurl: 1.0.2 352 | escape-html: 1.0.3 353 | on-finished: 2.4.1 354 | parseurl: 1.3.3 355 | statuses: 2.0.1 356 | unpipe: 1.0.0 357 | transitivePeerDependencies: 358 | - supports-color 359 | dev: true 360 | 361 | /fn.name/1.1.0: 362 | resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==} 363 | dev: true 364 | 365 | /follow-redirects/1.15.1: 366 | resolution: {integrity: sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==} 367 | engines: {node: '>=4.0'} 368 | peerDependencies: 369 | debug: '*' 370 | peerDependenciesMeta: 371 | debug: 372 | optional: true 373 | dev: false 374 | 375 | /form-data/4.0.0: 376 | resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} 377 | engines: {node: '>= 6'} 378 | dependencies: 379 | asynckit: 0.4.0 380 | combined-stream: 1.0.8 381 | mime-types: 2.1.35 382 | dev: false 383 | 384 | /forwarded/0.2.0: 385 | resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} 386 | engines: {node: '>= 0.6'} 387 | dev: true 388 | 389 | /fresh/0.5.2: 390 | resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} 391 | engines: {node: '>= 0.6'} 392 | dev: true 393 | 394 | /function-bind/1.1.1: 395 | resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} 396 | dev: true 397 | 398 | /generic-pool/3.8.2: 399 | resolution: {integrity: sha512-nGToKy6p3PAbYQ7p1UlWl6vSPwfwU6TMSWK7TTu+WUY4ZjyZQGniGGt2oNVvyNSpyZYSB43zMXVLcBm08MTMkg==} 400 | engines: {node: '>= 4'} 401 | dev: false 402 | 403 | /get-intrinsic/1.1.2: 404 | resolution: {integrity: sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==} 405 | dependencies: 406 | function-bind: 1.1.1 407 | has: 1.0.3 408 | has-symbols: 1.0.3 409 | dev: true 410 | 411 | /has-property-descriptors/1.0.0: 412 | resolution: {integrity: sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==} 413 | dependencies: 414 | get-intrinsic: 1.1.2 415 | dev: true 416 | 417 | /has-symbols/1.0.3: 418 | resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} 419 | engines: {node: '>= 0.4'} 420 | dev: true 421 | 422 | /has/1.0.3: 423 | resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} 424 | engines: {node: '>= 0.4.0'} 425 | dependencies: 426 | function-bind: 1.1.1 427 | dev: true 428 | 429 | /http-errors/2.0.0: 430 | resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} 431 | engines: {node: '>= 0.8'} 432 | dependencies: 433 | depd: 2.0.0 434 | inherits: 2.0.4 435 | setprototypeof: 1.2.0 436 | statuses: 2.0.1 437 | toidentifier: 1.0.1 438 | dev: true 439 | 440 | /iconv-lite/0.4.24: 441 | resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} 442 | engines: {node: '>=0.10.0'} 443 | dependencies: 444 | safer-buffer: 2.1.2 445 | dev: true 446 | 447 | /inherits/2.0.4: 448 | resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} 449 | dev: true 450 | 451 | /ipaddr.js/1.9.1: 452 | resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} 453 | engines: {node: '>= 0.10'} 454 | dev: true 455 | 456 | /is-arrayish/0.3.2: 457 | resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} 458 | dev: true 459 | 460 | /is-nan/1.3.2: 461 | resolution: {integrity: sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==} 462 | engines: {node: '>= 0.4'} 463 | dependencies: 464 | call-bind: 1.0.2 465 | define-properties: 1.1.4 466 | dev: true 467 | 468 | /is-stream/2.0.1: 469 | resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} 470 | engines: {node: '>=8'} 471 | dev: true 472 | 473 | /kuler/2.0.0: 474 | resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==} 475 | dev: true 476 | 477 | /logform/2.4.2: 478 | resolution: {integrity: sha512-W4c9himeAwXEdZ05dQNerhFz2XG80P9Oj0loPUMV23VC2it0orMHQhJm4hdnnor3rd1HsGf6a2lPwBM1zeXHGw==} 479 | dependencies: 480 | '@colors/colors': 1.5.0 481 | fecha: 4.2.3 482 | ms: 2.1.3 483 | safe-stable-stringify: 2.3.1 484 | triple-beam: 1.3.0 485 | dev: true 486 | 487 | /long-timeout/0.1.1: 488 | resolution: {integrity: sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w==} 489 | dev: true 490 | 491 | /luxon/1.28.0: 492 | resolution: {integrity: sha512-TfTiyvZhwBYM/7QdAVDh+7dBTBA29v4ik0Ce9zda3Mnf8on1S5KJI8P2jKFZ8+5C0jhmr0KwJEO/Wdpm0VeWJQ==} 493 | dev: true 494 | 495 | /media-typer/0.3.0: 496 | resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} 497 | engines: {node: '>= 0.6'} 498 | dev: true 499 | 500 | /merge-descriptors/1.0.1: 501 | resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==} 502 | dev: true 503 | 504 | /methods/1.1.2: 505 | resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} 506 | engines: {node: '>= 0.6'} 507 | dev: true 508 | 509 | /mime-db/1.52.0: 510 | resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} 511 | engines: {node: '>= 0.6'} 512 | 513 | /mime-types/2.1.35: 514 | resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} 515 | engines: {node: '>= 0.6'} 516 | dependencies: 517 | mime-db: 1.52.0 518 | 519 | /mime/1.6.0: 520 | resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} 521 | engines: {node: '>=4'} 522 | hasBin: true 523 | dev: true 524 | 525 | /moment/2.29.4: 526 | resolution: {integrity: sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==} 527 | dev: true 528 | 529 | /ms/2.0.0: 530 | resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} 531 | dev: true 532 | 533 | /ms/2.1.3: 534 | resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} 535 | dev: true 536 | 537 | /negotiator/0.6.3: 538 | resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} 539 | engines: {node: '>= 0.6'} 540 | dev: true 541 | 542 | /node-schedule/2.1.0: 543 | resolution: {integrity: sha512-nl4JTiZ7ZQDc97MmpTq9BQjYhq7gOtoh7SiPH069gBFBj0PzD8HI7zyFs6rzqL8Y5tTiEEYLxgtbx034YPrbyQ==} 544 | engines: {node: '>=6'} 545 | dependencies: 546 | cron-parser: 3.5.0 547 | long-timeout: 0.1.1 548 | sorted-array-functions: 1.3.0 549 | dev: true 550 | 551 | /nodemailer/6.7.8: 552 | resolution: {integrity: sha512-2zaTFGqZixVmTxpJRCFC+Vk5eGRd/fYtvIR+dl5u9QXLTQWGIf48x/JXvo58g9sa0bU6To04XUv554Paykum3g==} 553 | engines: {node: '>=6.0.0'} 554 | dev: true 555 | 556 | /object-assign/4.1.1: 557 | resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} 558 | engines: {node: '>=0.10.0'} 559 | dev: false 560 | 561 | /object-hash/2.2.0: 562 | resolution: {integrity: sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==} 563 | engines: {node: '>= 6'} 564 | dev: true 565 | 566 | /object-inspect/1.12.2: 567 | resolution: {integrity: sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==} 568 | dev: true 569 | 570 | /object-keys/1.1.1: 571 | resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} 572 | engines: {node: '>= 0.4'} 573 | dev: true 574 | 575 | /on-finished/2.4.1: 576 | resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} 577 | engines: {node: '>= 0.8'} 578 | dependencies: 579 | ee-first: 1.1.1 580 | dev: true 581 | 582 | /one-time/1.0.0: 583 | resolution: {integrity: sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==} 584 | dependencies: 585 | fn.name: 1.1.0 586 | dev: true 587 | 588 | /parseurl/1.3.3: 589 | resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} 590 | engines: {node: '>= 0.8'} 591 | dev: true 592 | 593 | /path-to-regexp/0.1.7: 594 | resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} 595 | dev: true 596 | 597 | /proxy-addr/2.0.7: 598 | resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} 599 | engines: {node: '>= 0.10'} 600 | dependencies: 601 | forwarded: 0.2.0 602 | ipaddr.js: 1.9.1 603 | dev: true 604 | 605 | /qs/6.10.3: 606 | resolution: {integrity: sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==} 607 | engines: {node: '>=0.6'} 608 | dependencies: 609 | side-channel: 1.0.4 610 | dev: true 611 | 612 | /range-parser/1.2.1: 613 | resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} 614 | engines: {node: '>= 0.6'} 615 | dev: true 616 | 617 | /raw-body/2.5.1: 618 | resolution: {integrity: sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==} 619 | engines: {node: '>= 0.8'} 620 | dependencies: 621 | bytes: 3.1.2 622 | http-errors: 2.0.0 623 | iconv-lite: 0.4.24 624 | unpipe: 1.0.0 625 | dev: true 626 | 627 | /readable-stream/3.6.0: 628 | resolution: {integrity: sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==} 629 | engines: {node: '>= 6'} 630 | dependencies: 631 | inherits: 2.0.4 632 | string_decoder: 1.3.0 633 | util-deprecate: 1.0.2 634 | dev: true 635 | 636 | /redis/4.3.1: 637 | resolution: {integrity: sha512-cM7yFU5CA6zyCF7N/+SSTcSJQSRMEKN0k0Whhu6J7n9mmXRoXugfWDBo5iOzGwABmsWKSwGPTU5J4Bxbl+0mrA==} 638 | dependencies: 639 | '@redis/bloom': 1.0.2_@redis+client@1.3.0 640 | '@redis/client': 1.3.0 641 | '@redis/graph': 1.0.1_@redis+client@1.3.0 642 | '@redis/json': 1.0.4_@redis+client@1.3.0 643 | '@redis/search': 1.1.0_@redis+client@1.3.0 644 | '@redis/time-series': 1.0.3_@redis+client@1.3.0 645 | dev: false 646 | 647 | /safe-buffer/5.2.1: 648 | resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} 649 | dev: true 650 | 651 | /safe-stable-stringify/2.3.1: 652 | resolution: {integrity: sha512-kYBSfT+troD9cDA85VDnHZ1rpHC50O0g1e6WlGHVCz/g+JS+9WKLj+XwFYyR8UbrZN8ll9HUpDAAddY58MGisg==} 653 | engines: {node: '>=10'} 654 | dev: true 655 | 656 | /safer-buffer/2.1.2: 657 | resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} 658 | dev: true 659 | 660 | /send/0.18.0: 661 | resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==} 662 | engines: {node: '>= 0.8.0'} 663 | dependencies: 664 | debug: 2.6.9 665 | depd: 2.0.0 666 | destroy: 1.2.0 667 | encodeurl: 1.0.2 668 | escape-html: 1.0.3 669 | etag: 1.8.1 670 | fresh: 0.5.2 671 | http-errors: 2.0.0 672 | mime: 1.6.0 673 | ms: 2.1.3 674 | on-finished: 2.4.1 675 | range-parser: 1.2.1 676 | statuses: 2.0.1 677 | transitivePeerDependencies: 678 | - supports-color 679 | dev: true 680 | 681 | /serve-static/1.15.0: 682 | resolution: {integrity: sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==} 683 | engines: {node: '>= 0.8.0'} 684 | dependencies: 685 | encodeurl: 1.0.2 686 | escape-html: 1.0.3 687 | parseurl: 1.3.3 688 | send: 0.18.0 689 | transitivePeerDependencies: 690 | - supports-color 691 | dev: true 692 | 693 | /setprototypeof/1.2.0: 694 | resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} 695 | dev: true 696 | 697 | /side-channel/1.0.4: 698 | resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} 699 | dependencies: 700 | call-bind: 1.0.2 701 | get-intrinsic: 1.1.2 702 | object-inspect: 1.12.2 703 | dev: true 704 | 705 | /simple-swizzle/0.2.2: 706 | resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} 707 | dependencies: 708 | is-arrayish: 0.3.2 709 | dev: true 710 | 711 | /sorted-array-functions/1.3.0: 712 | resolution: {integrity: sha512-2sqgzeFlid6N4Z2fUQ1cvFmTOLRi/sEDzSQ0OKYchqgoPmQBVyM3959qYx3fpS6Esef80KjmpgPeEr028dP3OA==} 713 | dev: true 714 | 715 | /stack-trace/0.0.10: 716 | resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==} 717 | dev: true 718 | 719 | /statuses/2.0.1: 720 | resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} 721 | engines: {node: '>= 0.8'} 722 | dev: true 723 | 724 | /string_decoder/1.3.0: 725 | resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} 726 | dependencies: 727 | safe-buffer: 5.2.1 728 | dev: true 729 | 730 | /text-hex/1.0.0: 731 | resolution: {integrity: sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==} 732 | dev: true 733 | 734 | /toidentifier/1.0.1: 735 | resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} 736 | engines: {node: '>=0.6'} 737 | dev: true 738 | 739 | /triple-beam/1.3.0: 740 | resolution: {integrity: sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==} 741 | dev: true 742 | 743 | /type-is/1.6.18: 744 | resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} 745 | engines: {node: '>= 0.6'} 746 | dependencies: 747 | media-typer: 0.3.0 748 | mime-types: 2.1.35 749 | dev: true 750 | 751 | /unpipe/1.0.0: 752 | resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} 753 | engines: {node: '>= 0.8'} 754 | dev: true 755 | 756 | /util-deprecate/1.0.2: 757 | resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} 758 | dev: true 759 | 760 | /utils-merge/1.0.1: 761 | resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} 762 | engines: {node: '>= 0.4.0'} 763 | dev: true 764 | 765 | /vary/1.1.2: 766 | resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} 767 | engines: {node: '>= 0.8'} 768 | 769 | /winston-daily-rotate-file/4.7.1_winston@3.8.1: 770 | resolution: {integrity: sha512-7LGPiYGBPNyGHLn9z33i96zx/bd71pjBn9tqQzO3I4Tayv94WPmBNwKC7CO1wPHdP9uvu+Md/1nr6VSH9h0iaA==} 771 | engines: {node: '>=8'} 772 | peerDependencies: 773 | winston: ^3 774 | dependencies: 775 | file-stream-rotator: 0.6.1 776 | object-hash: 2.2.0 777 | triple-beam: 1.3.0 778 | winston: 3.8.1 779 | winston-transport: 4.5.0 780 | dev: true 781 | 782 | /winston-transport/4.5.0: 783 | resolution: {integrity: sha512-YpZzcUzBedhlTAfJg6vJDlyEai/IFMIVcaEZZyl3UXIl4gmqRpU7AE89AHLkbzLUsv0NVmw7ts+iztqKxxPW1Q==} 784 | engines: {node: '>= 6.4.0'} 785 | dependencies: 786 | logform: 2.4.2 787 | readable-stream: 3.6.0 788 | triple-beam: 1.3.0 789 | dev: true 790 | 791 | /winston/3.8.1: 792 | resolution: {integrity: sha512-r+6YAiCR4uI3N8eQNOg8k3P3PqwAm20cLKlzVD9E66Ch39+LZC+VH1UKf9JemQj2B3QoUHfKD7Poewn0Pr3Y1w==} 793 | engines: {node: '>= 12.0.0'} 794 | dependencies: 795 | '@dabh/diagnostics': 2.0.3 796 | async: 3.2.4 797 | is-stream: 2.0.1 798 | logform: 2.4.2 799 | one-time: 1.0.0 800 | readable-stream: 3.6.0 801 | safe-stable-stringify: 2.3.1 802 | stack-trace: 0.0.10 803 | triple-beam: 1.3.0 804 | winston-transport: 4.5.0 805 | dev: true 806 | 807 | /yallist/4.0.0: 808 | resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} 809 | dev: false 810 | -------------------------------------------------------------------------------- /post.js: -------------------------------------------------------------------------------- 1 | // const { getBookListApi, postSignApi } = require("./api/juejinApiOld"); 2 | const { initHttpAxios, juejinApi } = require("./api/juejinApi"); 3 | initHttpAxios(); 4 | const { 5 | getBookList, 6 | postSign, 7 | touchHappy, 8 | getHappyCardList, 9 | postReadTask, 10 | getBookSectionOfVite, 11 | getBugListGame, 12 | postBugCollectGame, 13 | checkTodayStatus 14 | } = juejinApi; 15 | // https://www.baoxiaohe.com/api/design/search/popular 16 | const { bugFixGame } = require("./task/index"); 17 | // bugFixGame(); 18 | checkTodayStatus() 19 | .then((res) => { 20 | console.log(res); 21 | console.log(res.data); 22 | 23 | }) 24 | .catch((err) => { 25 | console.log(err); 26 | }); 27 | 28 | // postReadTask(); 29 | 30 | // getBookSectionOfVite().then((res) => { 31 | // let sectionsArr = res.data.sections; 32 | // let sectionsIds = []; 33 | // // 每天阅读任务完成数量上限为5 34 | // for (let i = 0; i < 5; i++) { 35 | // sectionsIds.push(sectionsArr[i].section_id); 36 | // } 37 | // console.log(sectionsIds); 38 | // sectionsIds.forEach((id) => { 39 | // postReadTask(id); 40 | // }); 41 | // }); 42 | -------------------------------------------------------------------------------- /redis/app.js: -------------------------------------------------------------------------------- 1 | const redis = require("redis"); // 引入 redis 2 | const dotenv = require("dotenv"); 3 | dotenv.config(); 4 | const redisClient = redis.createClient(6379, "127.0.0.1"); // 创建客户端 5 | // 监听错误信息 6 | redisClient.on("err", (err) => { 7 | console.log("redis client error: ", err); 8 | }); 9 | 10 | redisClient.on("ready", function (err) { 11 | console.log("ready"); 12 | }); 13 | 14 | const isRequiredPassword = process.env.authPass !== "YES"; 15 | const redisConnectHandle = async (callBack) => { 16 | try { 17 | const option = {}; 18 | if (isRequiredPassword) { 19 | option.auth_pass = process.env.PASSWORD; 20 | } 21 | if (!redisClient.isOpen) { 22 | await redisClient.connect(6379, "127.0.0.1", option); 23 | } 24 | const accountList = await getAccountList(); 25 | callBack && callBack(accountList); 26 | } catch (error) { 27 | throw new Error(error); 28 | } 29 | }; 30 | 31 | const addAccountList = async (data) => { 32 | redisClient 33 | .rPush("jjAccountTest", data) 34 | .then((res) => { 35 | console.log(res); 36 | }) 37 | .catch((err) => { 38 | console.log(err); 39 | }); 40 | }; 41 | const getAccountList = async () => { 42 | try { 43 | const result = await redisClient.lRange("jjAccount", 0, 10); 44 | let arrList = []; 45 | for (const key of result) { 46 | arrList.push(JSON.parse(key)); 47 | // '{"name":"mingo","token":"sadkjh"}'; 48 | } 49 | return arrList; 50 | } catch (error) { 51 | throw new Error(error); 52 | } 53 | }; 54 | 55 | // redisClient.quit(); 56 | //redisConnectHandle(); 57 | module.exports = redisConnectHandle; 58 | -------------------------------------------------------------------------------- /redis/redis.md: -------------------------------------------------------------------------------- 1 | ## mac 系统安装redis 2 | brew install redis 3 | 4 | redis-server 5 | brew services start redis 6 | brew services info redis 7 | brew services stop redis 8 | 9 | ## 基本指令 10 | redis官网 https://redis.io/docs/getting-started/installation/install-redis-on-mac-os/ 11 | 菜鸟教程 https://www.runoob.com/redis/redis-tutorial.html 12 | 13 | 打开Redis 客户端 14 | redis-cli 15 | 查看是否密码配置项 16 | CONFIG get requirepass 17 | 18 | CONFIG set requirepass "runoob" 19 | 20 | 登陆 21 | AUTH password 22 | 23 | 24 | get key 25 | set key 26 | del key 27 | 28 | 查看所有的key 29 | keys * 30 | 31 | 32 | if value is of type string -> GET 33 | if value is of type hash -> HGETALL 34 | if value is of type lists -> lrange 35 | if value is of type sets -> smembers 36 | if value is of type sorted sets -> ZRANGEBYSCORE 37 | if value is of type stream -> xread count streams . 38 | https://redis.io/commands/xread -------------------------------------------------------------------------------- /redis/添加数据指令.md: -------------------------------------------------------------------------------- 1 | 2 | 添加账户 3 | 4 | RPUSH jjAccount '{"name":"mingo","vip":true,"token":"aa"' 5 | 6 | 删除账户 7 | 8 | LREM key count VALUE 9 | count = 0 : 移除表中所有与 VALUE 相等的值。 10 | 11 | 12 | LPOP key 13 | 移出并获取列表的第一个元素 14 | 15 | RPOP key 16 | 移出并获取列表的最后一个元素 17 | 18 | 查看账户表 19 | 20 | lRange jjAccount 0 10 21 | -------------------------------------------------------------------------------- /task/index.js: -------------------------------------------------------------------------------- 1 | // 任务方法 (需要调用组合api共同完成,存在接口先后顺序) 2 | 3 | const { initHttpAxios, juejinApi } = require("../api/juejinApi"); 4 | // const fsBotApp = require("../utils/fsRobot"); 5 | const dayBotApp = require("../utils/dayApiRobot"); 6 | initHttpAxios(); 7 | const { 8 | postReadTask, 9 | getBookSectionOfVite, 10 | getBugListGame, 11 | postBugCollectGame, 12 | checkTodayStatus 13 | } = juejinApi; 14 | const taskMethods = { 15 | vipReadTask() { 16 | getBookSectionOfVite() 17 | .then((res) => { 18 | let sectionsArr = res.data.sections; 19 | let sectionsIds = []; 20 | // vip5 每天阅读任务完成数量上限为10 21 | for (let i = 0; i < 10; i++) { 22 | sectionsIds.push(sectionsArr[i].section_id); 23 | } 24 | sectionsIds.forEach((id) => { 25 | postReadTask(id); 26 | }); 27 | }) 28 | .catch((err) => { 29 | throw err; 30 | }); 31 | }, 32 | bugFixGame() { 33 | getBugListGame() 34 | .then((res) => { 35 | let list = res.data; 36 | list.forEach((item) => { 37 | let params = { 38 | bug_time: item.bug_time, 39 | bug_type: item.bug_type, 40 | }; 41 | postBugCollectGame(params); 42 | }); 43 | }) 44 | .catch((err) => { 45 | throw err; 46 | }); 47 | }, 48 | /** 49 | * 检查是否签到,未签到发送消息通知 50 | */ 51 | checkSign() { 52 | checkTodayStatus() 53 | .then((res) => { 54 | const { data = {} } = res 55 | if (!data.check_in_done) { 56 | // fsBotApp.sendSignTipMsg() 57 | dayBotApp.sendSignTipMsg() 58 | } 59 | }) 60 | .catch((err) => { 61 | throw err; 62 | }); 63 | 64 | } 65 | }; 66 | 67 | module.exports = taskMethods; 68 | -------------------------------------------------------------------------------- /utils/dayApiRobot.js: -------------------------------------------------------------------------------- 1 | // day机器人通知 2 | const { dayRobotApi, initDayHttpAxios } = require('../api/dayAPi') 3 | 4 | class dayBot { 5 | token 6 | http 7 | constructor(token) { 8 | this.token = token 9 | this.http = initDayHttpAxios(token) 10 | } 11 | sendSignTipMsg() { 12 | return this.http.get(dayRobotApi.sendSignMsg) 13 | } 14 | 15 | } 16 | const fsBotApp = new dayBot() 17 | 18 | module.exports = fsBotApp 19 | 20 | // 调试 21 | // let fsBot = new FsBot() 22 | fsBotApp.sendSignTipMsg().then(res => { 23 | console.log(res); 24 | }).catch(err => { 25 | console.log('err', err); 26 | }) -------------------------------------------------------------------------------- /utils/emailSend.js: -------------------------------------------------------------------------------- 1 | const nodemailer = require("nodemailer"); 2 | const { getLogger } = require("nodemailer/lib/shared"); 3 | 4 | let nodeMail = nodemailer.createTransport({ 5 | service: "qq", //类型qq邮箱 6 | port: 465, //上文获取的port 7 | secure: true, //上文获取的secure 8 | auth: { 9 | user: "mingo233@qq.com", // 发送方的邮箱,可以选择你自己的qq邮箱 10 | pass: "jaixkznfbxgsbdbc", // 上文获取的stmp授权码 11 | }, 12 | }); 13 | 14 | // node_modules/nodemailer/lib/well-known/services.json可以查看相关的配置, 15 | // 比如这里是qq邮箱,port为465,secure为true。 16 | 17 | const emailSend = (msg, url) => { 18 | console.log("邮件准备开始发送"); 19 | console.log(msg); 20 | 21 | const email = "825740725@qq.com"; 22 | //发送邮件 23 | const mail = { 24 | from: `mingo233@qq.com`, // 发件人 25 | subject: "科教兴国脚本错误日志", //邮箱主题 26 | to: email, //收件人 27 | // 邮件内容,用html格式编写 28 | html: ` 29 |

Error message

30 |

${msg}

31 | 32 | `, 33 | }; 34 | nodeMail.sendMail(mail, (err, info) => { 35 | if (!err) { 36 | console.log("验证发送成功"); 37 | console.log(info); 38 | } else { 39 | // res.json({ msg: "验证码发送失败,请稍后重试" }); 40 | console.log(err); 41 | } 42 | }); 43 | }; 44 | 45 | const emailTipSend = (config, url) => { 46 | 47 | const {email= "825740725@qq.com",subject } = config; 48 | //发送邮件 49 | const mail = { 50 | from: `mingo233@qq.com`, // 发件人 51 | subject: "juejin活跃检测提醒", //邮箱主题 52 | to: email, //收件人 53 | // 邮件内容,用html格式编写 54 | html: ` 55 |

Mingo提醒:

56 |

今天已完成签到,为防止官方活跃检测,建议点击下面链接进入掘金页面

57 |

https://juejin.cn/

58 | `, 59 | }; 60 | nodeMail.sendMail(mail, (err, info) => { 61 | if (!err) { 62 | console.log("验证发送成功"); 63 | console.log(info); 64 | } else { 65 | console.log(err); 66 | } 67 | }); 68 | }; 69 | module.exports = emailSend; 70 | -------------------------------------------------------------------------------- /utils/fsRobot.js: -------------------------------------------------------------------------------- 1 | // 飞书机器人通知 2 | const { fsRobotApi, initFsHttpAxios } = require('../api/fsApi') 3 | 4 | class FsBot { 5 | token 6 | http 7 | constructor(token) { 8 | this.token = token 9 | this.http = initFsHttpAxios(token) 10 | } 11 | sendSignTipMsg() { 12 | const params = 13 | { 14 | "msg_type": "interactive", 15 | "card": { 16 | "elements": [ 17 | { 18 | "tag": "div", 19 | "text": { 20 | "content": "**掘金**:检测到今天还未签到 ", 21 | "tag": "lark_md" 22 | } 23 | }, 24 | { 25 | "actions": [ 26 | { 27 | "tag": "button", 28 | "text": { 29 | "content": "点进签到 :玫瑰:", 30 | "tag": "lark_md" 31 | }, 32 | "url": "snssdk2606://juejin.cn/user/center/signin?from=main_page", 33 | "type": "default", 34 | "value": {} 35 | } 36 | ], 37 | "tag": "action" 38 | } 39 | ], 40 | "header": { 41 | "title": { 42 | "content": "每日提醒", 43 | "tag": "plain_text" 44 | } 45 | } 46 | } 47 | } 48 | return this.http.post(fsRobotApi.sendMsg, params) 49 | } 50 | 51 | } 52 | const fsBotApp = new FsBot() 53 | 54 | module.exports = fsBotApp 55 | 56 | // 调试 57 | // let fsBot = new FsBot() 58 | // fsBot.sendSignTipMsg().then(res => { 59 | // console.log(res); 60 | // }).catch(err => { 61 | // console.log(err); 62 | // }) -------------------------------------------------------------------------------- /utils/ioTxt.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | 3 | const path = require("path"); 4 | 5 | const isExists = (filePath) => { 6 | return new Promise((resolve, reject) => { 7 | fs.access(filePath, fs.constants.F_OK, (err) => { 8 | // if (err) console.log(`${file} ${err ? "does not exist" : "exists"}`);s 9 | if (!err) { 10 | resolve(true); 11 | } else { 12 | resolve(false); 13 | } 14 | }); 15 | }); 16 | }; 17 | const getLogIndex = (name) => { 18 | let result = name.match(/[0-9]/); 19 | if (result) { 20 | return result[0]; 21 | } else { 22 | return null; 23 | } 24 | }; 25 | 26 | const inputHandle = (content) => { 27 | // const content = fs.readFileSync("./src/template.jsx"); 28 | let fileArrs = fs.readdirSync(path.resolve(__dirname, `log`)); 29 | let n = getLogIndex(fileArrs[fileArrs.length - 1]); 30 | if (!n) { 31 | fs.rmdirSync("./log", { recursive: true, force: true }); 32 | fs.mkdirSync("./log"); 33 | n = 1; 34 | } else { 35 | n = Number(n) + 1; 36 | } 37 | 38 | let fileName = `log-${n}.json`; 39 | const filePath = path.resolve(__dirname, `log/${fileName}`); 40 | // const filePath2 = path.resolve(__dirname, `log/log.txt`); 41 | console.log(filePath); 42 | console.log("日志信息已记录"); 43 | fs.writeFileSync(filePath, content); 44 | }; 45 | 46 | module.exports = inputHandle; 47 | -------------------------------------------------------------------------------- /utils/node-schedule.js: -------------------------------------------------------------------------------- 1 | const schedule = require("node-schedule"); 2 | 3 | class Interval { 4 | constructor() { 5 | } 6 | // 生成新的定时任务 7 | create(unit_name, maintain_time, callback) { 8 | let isExist = this.findOne(unit_name) 9 | if (isExist) { 10 | // 若已存在该任务,则先删除 11 | this.delete(unit_name) 12 | } 13 | schedule.scheduleJob( 14 | unit_name, 15 | maintain_time, 16 | callback 17 | ); 18 | } 19 | // 删除定时任务 20 | delete(unit_name) { 21 | if (schedule.scheduledJobs[unit_name]) { 22 | schedule.scheduledJobs[unit_name].cancel(); 23 | return true; 24 | } 25 | return false; 26 | } 27 | // 找到一个定时任务 28 | findOne(name) { 29 | if (schedule.scheduledJobs[name]) { 30 | return schedule.scheduledJobs[name]; 31 | } else { 32 | // throw new Error("未找到任务名"); 33 | return false 34 | } 35 | } 36 | // 查看所有的定时任务 37 | findAll() { 38 | return schedule.scheduledJobs; 39 | } 40 | } 41 | 42 | // 定时任务 43 | 44 | // let scheduleApp = new Interval(); 45 | // scheduleApp.create( 46 | // "自动分发任务 0 0 12 * * *", 47 | // "10 * * * * *", 48 | // () => { 49 | // // 写入你自己想在定时任务触发的时候,想要执行的函数 50 | // console.log("nihao") 51 | // }); 52 | // scheduleApp.create( 53 | // "自动分发任务 0 0 13 * * *", 54 | // "10 * * * * *", 55 | // () => { 56 | // // 写入你自己想在定时任务触发的时候,想要执行的函数 57 | // console.log("nihao") 58 | // }); 59 | 60 | // console.log(scheduleApp.findAll()); 61 | // * * * * * * 62 | // ┬ ┬ ┬ ┬ ┬ ┬ 63 | // │ │ │ │ │ │ 64 | // │ │ │ │ │ └ day of week (0 - 7) (0 or 7 is Sun) 65 | // │ │ │ │ └───── month (1 - 12) 66 | // │ │ │ └────────── day of month (1 - 31) 67 | // │ │ └─────────────── hour (0 - 23) 68 | // │ └──────────────────── minute (0 - 59) 69 | // └───────────────────────── second (0 - 59, OPTIONAL) 70 | 71 | // 每分钟的第30秒触发: '30 * * * * *' 72 | 73 | // 每小时的1分30秒触发 :'30 1 * * * *' 74 | 75 | // 每天的凌晨1点1分30秒触发 :'30 1 1 * * *' 76 | 77 | // 每月的1日1点1分30秒触发 :'30 1 1 1 * *' 78 | 79 | // 2016年的1月1日1点1分30秒触发 :'30 1 1 1 2016 *' 80 | 81 | // 每周1的1点1分30秒触发 :'30 1 1 * * 1' 82 | 83 | module.exports = Interval; 84 | -------------------------------------------------------------------------------- /utils/tools.js: -------------------------------------------------------------------------------- 1 | //解决对象中存在循环引用问题 2 | function circularReference(row) { 3 | let cache = []; 4 | let str = JSON.stringify(row, function (key, value) { 5 | if (typeof value === "object" && value !== null) { 6 | if (cache.indexOf(value) !== -1) { 7 | // 移除 8 | return; 9 | } // 收集所有的值 10 | cache.push(value); 11 | } 12 | return value; 13 | }); 14 | cache = null; // 清空变量,便于垃圾回收机制回收 15 | return str; 16 | } 17 | 18 | const sleep = (time = 5000) => { 19 | return new Promise((resolve, reject) => { 20 | setTimeout(() => { 21 | resolve(); 22 | }, time); 23 | }); 24 | }; 25 | module.exports = { 26 | circularReference, 27 | sleep, 28 | }; 29 | -------------------------------------------------------------------------------- /utils/winston.js: -------------------------------------------------------------------------------- 1 | const winston = require("winston"); 2 | const path = require("path"); 3 | require("winston-daily-rotate-file"); 4 | 5 | // doc https://juejin.cn/post/6865926810061045774 6 | // https://github.com/winstonjs/winston-daily-rotate-file 7 | 8 | const logger = winston.createLogger({ 9 | format: winston.format.combine( 10 | winston.format.label({ label: "juejin" }), 11 | winston.format.timestamp({ format: "YYYY-MM-DD hh:mm:ss.SSS A" }), 12 | winston.format.prettyPrint() 13 | ), 14 | transports: [ 15 | // new winston.transports.Console(), 16 | // new winston.transports.File({ filename: "combined.log" }), 17 | new winston.transports.DailyRotateFile({ 18 | filename: path.join(__dirname, "..", "logs", `%DATE%.log`), 19 | datePattern: "YYYY-MM-DD", 20 | utc: true, 21 | }), 22 | ], 23 | }); 24 | // logger.info({ name: "aa", age: 99 }); 25 | 26 | module.exports = logger; 27 | --------------------------------------------------------------------------------