首页
归档
关于
友人帐
Search
1
Mac OS 终端利器 iTerm2 + Oh My Zsh
13,894 阅读
2
前端 History 路由及 API 反向代理的 Nginx 配置
10,595 阅读
3
解决 Xcode Command Line Tools 错误或无法安装问题
6,246 阅读
4
Mac下右键使用VSCode打开项目
3,944 阅读
5
BrowserHistory刷新页面404问题
3,430 阅读
码上世界
内容分享
生活印记
其他
登录
Search
小小孩
累计撰写
95
篇文章
累计收到
181
条评论
首页
栏目
码上世界
内容分享
生活印记
其他
页面
归档
关于
友人帐
搜索到
48
篇与
码上世界
的结果
2024-11-11
前端项目路径别名终极解决方案
原生路径解决从 Node.js v12.19.0 开始,开发人员可以使用 Subpath Imports 在 npm 包中声明路径别名。这可以通过 package.json 文件中的 imports 字段来完成。不需要在 npm 上发布包。在任何目录中创建一个 package.json 文件就足够了。因此,这种方法也适用于私人项目。配置路径别名,假如有项目结构如下:my-project ├── src/ │ ├── components/ │ │ └── searchForm/ │ │ └── form/ │ │ └── index.ts │ ├── pages/ │ │ └── login/ │ │ └── about/ │ │ └── home/ │ └── mock/ │ └── api/ │ └── index.ts └── package.json我们可以在 package.json 中这么配置:{ "name": "my-awesome-project", "imports": { "#*": "./*" } }接下来就是愉快的使用了:import { SearchForm } from "#src/components/searchForm";
2024年11月11日
13 阅读
0 评论
0 点赞
2024-03-29
http 协议头 Content-Disposition 的作用
在响应中 Content-Disposition 是真正的作为消息头的一部分,它主要有两种用法:inline当它的值为 inline 时,表示响应的消息作为 HTML 页面的一部分(inline 是默认值)。假设你本身想要下载一个 PDF 文件,但是你将 Content-Disposition 的值设置为 inline 或者没设置,你的响应头对应如下:Content-Type: application/pdf Content-Disposition: inline; filename="example.pdf"此时浏览器不会去下载这个文件,而是直接在浏览器中去打开这个 PDF 文件。相信这样的场景大家会熟悉,我们经常去浏览一个学术完整的 PDF 链接时都是直接在浏览器中打开 PDF 文件,而不是下载到本地,这就是设置了 Content-Disposition: inline 的原因(即使没设置它的默认值也是这个)。attachment现在再来看下 Content-Disposition: attachment,这个是真正意义上的文件下载。还是以前面的响应头为例:Content-Type: application/pdf Content-Disposition: attachment; filename="example.pdf"此时,当服务器给客户端(通常是浏览器)响应时,它会有一个弹窗提示,提醒你保存文件。而保存的文件的默认名就是 filename 指定的值,当然该属性是非必须的。
2024年03月29日
313 阅读
3 评论
0 点赞
2024-02-27
LNMP Nginx 安装 brotli 模块
lnmp 安装wget https://soft.lnmp.com/lnmp/lnmp2.0.tar.gz -O lnmp2.0.tar.gz && tar zxf lnmp2.0.tar.gz && cd lnmp2.0 && ./install.sh lnmpnginx 安装 brotlicd /root git clone --recurse-submodules -j8 https://gitee.com/hipi/ngx_brotli.git cd ngx_brotli/deps/brotli mkdir out && cd out cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DCMAKE_C_FLAGS="-Ofast -m64 -march=native -mtune=native -flto -funroll-loops -ffunction-sections -fdata-sections -Wl,--gc-sections" -DCMAKE_CXX_FLAGS="-Ofast -m64 -march=native -mtune=native -flto -funroll-loops -ffunction-sections -fdata-sections -Wl,--gc-sections" -DCMAKE_INSTALL_PREFIX=./installed .. cmake --build . --config Release --target brotlienc cd /root编辑lnmp安装包下的/root/lnmp2.0/lnmp.conf在 Nginx_Modules_Options=" 的引号内加上--add-module=/root/ngx_brotli保存./upgrade.sh nginx升级一下nginx,版本号填写当前版本号就行升级完成就支持broti了,
2024年02月27日
186 阅读
0 评论
0 点赞
2023-11-02
Nginx 安装 brotli 模块
前置条件cd /www/server git clone --recurse-submodules -j8 https://gitee.com/hipi/ngx_brotli.git cd ngx_brotli/deps/brotli mkdir out && cd out cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DCMAKE_C_FLAGS="-Ofast -m64 -march=native -mtune=native -flto -funroll-loops -ffunction-sections -fdata-sections -Wl,--gc-sections" -DCMAKE_CXX_FLAGS="-Ofast -m64 -march=native -mtune=native -flto -funroll-loops -ffunction-sections -fdata-sections -Wl,--gc-sections" -DCMAKE_INSTALL_PREFIX=./installed .. cmake --build . --config Release --target brotlienc cd ../../../..安装方法 1echo "--add-module=/www/server/ngx_brotli" > /www/server/panel/install/nginx_configure.pl面板直接编译安装方法 2模块名称:ngx_brotli模块描述:ngx_brotli模块参数:--add-module=/www/server/ngx_brotli前置脚本不填配置# 启用 Brotli 压缩 brotli on; # 设置 Brotli 压缩级别 brotli_comp_level 6; # 设置启用压缩的最小文件大小 brotli_min_length 20; # 配置 Brotli 压缩的缓冲区大小 brotli_buffers 16 8k; # 指定要压缩的文件类型 brotli_types text/xml text/plain text/css application/javascript application/x-javascript application/rss+xml text/javascript image/tiff image/svg+xml application/json application/xml;
2023年11月02日
492 阅读
3 评论
0 点赞
2023-08-30
JS 深拷贝终结者 structuredClone
之前的 JS 数据深拷贝,都是要么递归要么是利用 MessageChannel 来实现深克隆,现在终结者来了,JS 原生支持的函数 structuredClone ,可以实现数据深拷贝StructuredClone APIstructuredClone 是结构化拷贝算法的实现,能够实现几乎对所有数据类型的深拷贝。语法structuredClone(value) structuredClone(value, { transfer })参数value被克隆的对象。可以是任何结构化克隆支持的类型。transfer 可选是一个可转移对象的数组,里面的 值 并没有被克隆,而是被转移到被拷贝对象上。限制但是也有一些限制不允许克隆Error、Function和DOM对象,如果对象中含有,将抛出DATA_CLONE_ERR异常。不保留RegExp 对象的 lastIndex 字段。不保留属性描述符,setters 以及 getters(以及其他类似元数据的功能)。例如,如果4. 一个对象用属性描述符标记为 read-only,它将会被复制为 read-write。不保留原形链。兼容性Chrome >= 98FireFox >= 94
2023年08月30日
234 阅读
1 评论
0 点赞
2023-08-02
自定义可以异步等待的 forEach
自定义可以异步等待的 forEachArray.prototype.canAwaitForEach = async function(cb,thisArg){ for(let i = 0; i<this.length;i++){ await cb.call(thisArg,this[i],i,this) } }
2023年08月02日
125 阅读
0 评论
0 点赞
2023-05-26
清除 npx 缓存
为何需要清除 npx 缓存在新建模板时,比如 npm init vue@3 , 会下载 create-vue 包在 npx 缓存中,并且有概率不会根据create-vue包的小版本更新而更新,导致运行 npm init vue@3 都是旧的模板代码。如果发现这种问题就需要删除 npx 缓存,重新获取 create-vue 包。如何删除 npx 缓存查看本地 npm 缓存文件的路径:npm config get cache然后打开上述命令返回的文件夹路径,删除_npx 文件夹即可清除;其它方式还可以借助 clear-npx-cache 实现:npx clear-npx-cache
2023年05月26日
388 阅读
0 评论
1 点赞
2023-02-28
扩展 vis-network 右键菜单
之前一直使用 antv/g6 作为拓扑图展示插件,但发现 antv/g6 在力导向布局拖动下展示效果很差,节点一直在抖,而且在大数据下,直接卡死。所以转换为 vis-network ,相比之下 vis-network 效果很流畅,展示效果也不错。 唯一不好就是 没有 G6 那么多插件,就自己根据 G6 的插件api封装形式,自己封装了一份 vis 的右键菜单插件插件代码function modifyCSS(dom, css) { if (dom) { for (let key in css) { dom.style[key] = css[key]; } } return dom; } export default class VisMenu { constructor(network, config = {}) { this.network = network; const defaultConfig = { offsetX: 6, offsetY: 6, handleMenuClick: undefined, getContent: (e) => { return ` <div class='aa'> <div>菜单项1</div> <div>菜单项2</div> </div> `; }, onHide() { return true; }, itemTypes: ["node", "edge", "canvas"], }; this.config = { ...defaultConfig, ...config }; const _this = this; // 初始化menuDom this._createMenu(); this.network.on("oncontext", function (params) { params.event.preventDefault(); _this._showMenu(params); }); } _createMenu() { const _this = this; const containerDom = _this.network.canvas.frame; modifyCSS(containerDom, { position: "relative" }); const menuDom = document.createElement("div"); menuDom.className = _this.config.className || "vis-contextmenu"; containerDom.appendChild(menuDom); modifyCSS(menuDom, { top: "0px", position: "absolute", visibility: "hidden", }); _this.menuDom = menuDom; _this.containerDom = containerDom; } _showMenu(params) { const _this = this; const { pointer } = params; const menuResParams = {}; const atNode = _this.network.getNodeAt({ x: pointer.DOM.x, y: pointer.DOM.y, }); const atEdge = _this.network.getEdgeAt({ x: pointer.DOM.x, y: pointer.DOM.y, }); if (atNode) { menuResParams.type = "node"; menuResParams.id = atNode; menuResParams.orgItemData = _this.network.body.data.nodes.get(atNode); } else if (atEdge) { menuResParams.type = "edge"; menuResParams.id = atEdge; const orgItemData = _this.network.body.data.edges.get(atEdge); const fromId = orgItemData.from; const toId = orgItemData.to; menuResParams.orgItemData = orgItemData; menuResParams.orgFromData = _this.network.body.data.nodes.get(fromId); menuResParams.orgToData = _this.network.body.data.nodes.get(toId); } else { menuResParams.type = "canvas"; } // 判断是否显示菜单 if (!_this.config.itemTypes.includes(menuResParams.type)) { this._hideMenu(); return false; } // 塞入menuDom const menuStr = _this.config.getContent(menuResParams); _this.menuDom.innerHTML = menuStr; const graphWidth = _this.containerDom.offsetWidth; const graphHeight = _this.containerDom.offsetHeight; // Vis 默认在父元素添加 position:relative 所以不需要计算 offsetTop,offsetLeft // const graphTop = _this.containerDom.offsetTop; // const graphLeft = _this.containerDom.offsetLeft; // const bbox = _this.menuDom.getBoundingClientRect(); // let x = pointer.DOM.x + graphLeft + _this.config.offsetX; // let y = pointer.DOM.y + graphTop + _this.config.offsetY; // // when the menu is (part of) out of the DOM // if (x + bbox.width > graphWidth) { // x = pointer.DOM.x - bbox.width - _this.config.offsetX + graphLeft; // } // if (y + bbox.height > graphHeight) { // y = pointer.DOM.y - bbox.height - _this.config.offsetY + graphTop; // } const bbox = _this.menuDom.getBoundingClientRect(); let x = pointer.DOM.x + _this.config.offsetX; let y = pointer.DOM.y + _this.config.offsetY; if (x + bbox.width > graphWidth) { x = pointer.DOM.x - bbox.width - _this.config.offsetX; } if (y + bbox.height > graphHeight) { y = pointer.DOM.y - bbox.height - _this.config.offsetY; } modifyCSS(_this.menuDom, { top: `${y}px`, left: `${x}px`, visibility: "visible", }); const handler = () => { _this._hideMenu(); }; _this.handler = handler; // 如果在页面中其他任意地方进行click, 隐去菜单 document.body.addEventListener("click", handler); if (_this.config.handleMenuClick) { const handleMenuClickWrapper = (evt) => { _this.config.handleMenuClick(evt.target, menuResParams, _this.network); }; _this.menuDom.addEventListener("click", handleMenuClickWrapper); _this.handleMenuClickWrapper = handleMenuClickWrapper; } } _removeMenuEventListener() { const _this = this; if (_this.handler) { document.body.removeEventListener("click", _this.handler); } if (_this.handleMenuClickWrapper) { _this.menuDom.removeEventListener("click", _this.handleMenuClickWrapper); } } _hideMenu() { if (this.menuDom) { modifyCSS(this.menuDom, { visibility: "hidden" }); } // 隐藏菜单后需要移除事件监听 this._removeMenuEventListener(); } destroy() { this._removeMenuEventListener(); if (this.menuDom) { this.containerDom.removeChild(this.menuDom); } } } 如何使用import { Network } from "vis-network"; import VisMenu from './visMenu' const myNetwork = new Network(DOM, data, {/*...options*/}); new VisMenu(myNetwork, { className: "my-network-menu", getContent(e) { return `<ul> <li>${e.type}</li> <li>菜单一</li> <li>菜单二</li> <li>菜单三</li> <li>菜单四</li> <ul>`; }, handleMenuClick(target, item, network) { console.log(target.innerText); }, itemTypes: ["node", "edge"], });
2023年02月28日
288 阅读
1 评论
0 点赞
2022-12-26
利用 MessageChannel 来实现深克隆
前言一般 JS 深度克隆有两种方法 ,一种是 JSON 序列与反序列,另一种是 lodash 提供的 deepClone,其中利用 JSON 克隆 有以下缺点属性值为函数和 undefined 的属性会丢失属性值为正则表达式的会变成{}属性值为时间对象的会变成时间字符串其实另一种方法来实现克隆,利用MessageChannel消息通道的数据传输也可以实现对象深拷贝。MessageChannel利用 MessageChannel() 构造函数返回一个新的 MessageChannel 对象,其中返回的对象中包含两个 MessagePort 对象。这就是两个通道,其中可以数据传输。具体实现如下:function deepClone(obj) { return new Promise((resolve, reject) => { const { port1, port2 } = new MessageChannel(); port1.postMessage(obj); port2.onmessage = (msg) => { resolve(msg.data); }; port2.onerror = (msg) => { reject(msg.data); }; }); }使用(async () => { function deepClone(obj) { return new Promise((resolve, reject) => { const { port1, port2 } = new MessageChannel(); port1.postMessage(obj); port2.onmessage = (msg) => { resolve(msg.data); }; port2.onerror = (msg) => { reject(msg.data); }; }); } const obj = { a: 1, b: /\d/, c: new Date(), d: undefined, }; const cloneObj = await deepClone(obj); console.log(cloneObj, cloneObj === obj); })();利用 MessageChannel 来实现克隆 可以解决 大部分 JSON 克隆的缺点,但还是有个缺点是函数不能拷贝。 如果你所需拷贝的对象含有内置类型并且不包含函数,就可以利用 MessageChannel 来实现克隆。如果函数也要拷贝, 还是使用lodash 通过递归处理的 deepClone 方法。
2022年12月26日
286 阅读
0 评论
0 点赞
2022-12-26
自建 prerender 服务来对 SPA 项目做 SEO 优化
背景单页面应用使用浏览器动态解析 JS,呈现出最终的页面,用户体验比较好,网站性能也提高不少。但网络爬虫并不会动态解析 Js (谷歌比较先进可以解析 JS),所以访问所有未处理的单页面应用 URL 得到的只会是项目入口(index.html)文件中的代码,不能得到具体的内容,对 Seo 的优化极其的不友好。为了优化项目 Seo,目前主要有两个方案:服务端渲染(Server Side Rendering)、预渲染(Prerending)。使用服务端渲染的话 需要对项目进行改造,也违背了开始做 SPA 的初衷,这里就不做深入了,这里提供了一种专门针对搜索引擎爬虫的渲染 SEO 优化预渲染使用 Prerender.io 做网站预渲染,可以将网站页面渲染之后再返回给网络爬虫,间接完成网页的解析。 Prerender 相较于其他的解决方案,配置相对要简单一些,不用修改项目源码,代码零侵入,是一个不错的解决方案。可以使用访问 htttps:/prerender.io 就可以按照步骤接入,但是这事商业化的方案,使用量较多时就需要付费了,免费的计划 适合小型项目,看个人需求。另一种是可以自建 prerender 服务 可以为多个项目进行 预渲染自建 Prerender 服务选用的是 Prerender.io 开源渲染方案 再加上自己的优化处理。这里就主要介绍搭建基于 Centos 7 和 Nginx 环境的 Prerender 渲染服务,完成项目中网页的预渲染安装 Prerender 服务在服务器上安装 Node 环境在服务器上安装 Chrome在 Node 部署 Perender 服务安装 Node 环境第一步安装 Node 环境 我就不多赘述了 网上教程一大堆安装 ChromeCentos 系统下安装配置 yum 源 因为国内无法访问 Google,所以需要自己配置 yum 源,在目录 /etc/yum.repos.d/ 下新建google-chrome.repo文件cd /ect/yum.repos.d/ touch google-chrome.repo写入内容 google-chrome.repo[google-chrome] name=google-chrome baseurl=http://dl.google.com/linux/chrome/rpm/stable/$basearch enabled=1 gpgcheck=1 gpgkey=https://dl-ssl.google.com/linux/linux_signing_key.pub安装运行# 国内推荐 yum -y install google-chrome-stable --nogpgcheck检查安装安装成功后,Chrome的安装路径应该是 /opt/google/chrome .默认情况下,root用户不能直接运行chrome,所以可以 使用 adduser www新建另一个用户来运行,这里新建了 新的 www 用户。cd /opt/google/chrome su www ./chromedebian 系统下安装需要通过apt在终端中运行以下命令来确保您的系统是最新的sudo apt update sudo apt upgrade从官方存储库安装Google Chrome,需要添加Google的GPG密钥:wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | sudo apt-key add -将Google Chrome存储库添加到您的Debian系统sudo sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list'使用以下命令安装Google Chrome浏览器64位稳定版sudo apt update sudo apt install google-chrome-stable其实不一定非要安装 Chrome ,开源的 chromium 也行安装sudo apt update sudo apt install chromium找出位置 一般在 /usr/bin/chromiumwhich chromium配置项目的chrome 地址把得到的地址填入 chromeLocationconst server = prerender({ chromeLocation: '/usr/bin/chromium' }); 部署 Perender 服务本地新建项目└─node_modules └─index.js └─package.json先安装 prerender 木块npm i prerender编写 index.jsindex.jsconst prerender = require('prerender'); const server = prerender({ port:26666 }); server.start();后台运行nohup node ./server.js &如此就在 本地 26666 端口 运行了。安装中间件,这里使用 nginx 作为处理中间件server { listen 80; server_name example.com; root /path/to/your/root; index index.html; location / { try_files $uri @prerender; } location @prerender { # 如果使用 商业的 prerender.io 需要 将 YOUR_TOKEN替换为你的个人token proxy_set_header X-Prerender-Token YOUR_TOKEN; set $prerender 0; # 这里判断哪些爬虫需要走预渲染 if ($http_user_agent ~* "googlebot|bingbot|yandex|baiduspider") { set $prerender 1; } # 测试用 if ($args ~ "_escaped_fragment_") { set $prerender 1; } if ($http_user_agent ~ "Prerender") { set $prerender 0; } # 静态文件不需要预渲染 if ($uri ~* "\.(js|css|xml|less|png|jpg|jpeg|gif|pdf|doc|txt|ico|rss|zip|mp3|rar|exe|wmv|doc|avi|ppt|mpg|mpeg|tif|wav|mov|psd|ai|xls|mp4|m4a|swf|dat|dmg|iso|flv|m4v|torrent|ttf|woff|svg|eot)") { set $prerender 0; } #resolve using Google's DNS server to force DNS resolution and prevent caching of IPs resolver 8.8.8.8; if ($prerender = 1) { # 后续自建将 http://service.prerender.io 替换为自己的prerender服务,如 http://127.0.0.1:26666 set $prerenderServer "http://service.prerender.io"; rewrite .* /$scheme://$host$request_uri? break; proxy_pass $prerenderServer; } if ($prerender = 0) { rewrite .* /index.html break; } } }nginx 配置如上,看注释 自及修改自己的配置,检测nginx配置,并重启nginx。至此,Prerender 服务已经安装并启动成功。测试通过curl命令测试curl http://www.example.com/?_escaped_fragment_= 一般情况下就可看到 实际运行渲染的 html 结构了也可以 在网上找一个 在线模拟百度爬虫的网站 对网站进行模拟访问一下,看看 html 结构其他优化缓存优化同一个页面 每次 进来 都要进过chrome 来进行渲染,这很影响性能,这时可以通过缓存在优化,在有效时间内,如果有缓存直接取缓存数据返回需要编写缓存处理在 node 项目中安装 cache-manager-fs 模块npm i cache-manager-fs添加 插件 文件夹 及文件└─node_modules └─plugins │ └─fs-cache.js └─index.js └─package.jsonfs-cache.jsvar cacheManager = require('cache-manager'); var fsStore = require('cache-manager-fs'); module.exports = { init: function () { this.cache = cacheManager.caching({ store: fsStore, maxsize: process.env.CACHE_MAXSIZE || 500 * 1000 * 1000 * 1000, ttl: process.env.CACHE_TTL || 24*60*60, /*seconds*/ path: 'cache', preventfill: true }); }, requestReceived: function (req, res, next) { this.cache.get(req.prerender.url, function (err, result) { if (!err && result) { req.prerender.cacheHit = true; console.log('cacheHit 缓存命中', req.prerender.url) res.send(200, result); } else { next(); } }); }, beforeSend: function (req, res, next) { if (!req.prerender.cacheHit && req.prerender.statusCode == 200) { this.cache.set(req.prerender.url, req.prerender.content); } next(); } };其中 maxsize ttl path 可以通过 环境变量设置也可以 使用默认设置相应的 index.js 也需要小小修改const prerender = require('prerender'); const fsCache = require('./plugins/fs-cache.js'); const server = prerender({ port:26666 }); server.use(fsCache) server.start(); 重启 服务就可以实现了接口保护如果你的服务需要收到保护,防止别人恶意使用 可以像 prerender.io 添加接口 token 保护修改 index.jsconst prerender = require('prerender'); const fsCache = require('./plugins/fs-cache.js'); const server = prerender({ port:26666 }); server.use({ requestReceived: (req, res, next) => { const canPassToken = [] //这里放 可以通过的token const token = req.headers['x-prerender-token']; // 这里使用与 prerender.io 一样的头部字段名 if (!canPassToken.includes(token)) { // 如果 token 不在 数据里 则返回403 res.send(403) } else { next() } }, }) server.use(fsCache) server.start();
2022年12月26日
632 阅读
2 评论
0 点赞
1
2
...
5