使用 github 快速搭建属于自己的图床

发表于 3年以前  | 总阅读数:407 次

起因

说起来,图床应用这东西,在github上有很多,但是大多都是基于一些云厂商免费的静态存储服务来实现的,比如七牛云的静态存储,考虑到这些云厂商的赚钱欲望,所以我并不放心将他们作为图床的服务提供商。

也有支持github的,比如picgo,不过涉及到personal token,我也不是很放心将自己的token写入到一个开源项目的桌面应用里。而且picgo导出的github图片链接是以 githubusercontent.com 为host的链接,众所周知,该域名在中国很多地区都被DNS污染了,只有通过改host或是科学上网进行访问,所以结论是,picgo基于github导出的图片链接,在国内等于没用。

那有没有一种方式,既能让图片链接不被DNS污染或是被墙掉,又不会涉及到开发者personal token,影响账户安全呢?

于是,就有了picpic。picpic是我在做一个另一个大型的开源项目的过程中抽空实现的,初始版本只用了两天就写出来了,但是我本人自认为是一个合格和还不错的product maker,并不愿意产出一个使用繁琐,功能残缺的半成品给别人使用——关键是自己用的也不爽。

我做产品,核心观点就是,做出来的东西自己愿不愿意用,用起来有没有感受到“美”,是不是能够沉静在产品中去感受它,这很重要,正是因为我从没将自己定位成一个前端,或是node开发,而是product maker,终极理想就是artist,就是做艺术,内心始终有一个想法:你不是在写代码,你是在画一幅画,你享受这个过程,如果能够让别人享受到“结果”,那是再好不过了。

所以就有了它:

DEMO地址:https://matrixage.github.io/picpic_example/

项目地址:https://github.com/MatrixAges/picpic

picpic

基于离线版本,脱离了webpack的vue.js构建的单页面应用,原理就是通过node把图片数据预编译并写入到window对象中,然后通过chunk进行分片,提供翻页功能,至于文件夹模式,则是通过node把assets文件夹下的文件结构预编译成树形数据,写入到window对象,然后给页面中的js进行调用。

服务基于github pages,自动化构建使用的是github actions,通过自动化构建部署,部署静态文件到gh-pages分支,然后访问 username.github.io/${repo}/${img_path} 即可访问不被枪的静态图片。

几经打磨,最后我把它做成了cli,你只需要npm i @matrixage/picpic,即可使用。

下面讲讲,我是如何通过node和vue构建这样一个单页面应用的。

没有webpack的web应用

使用github actions也有一段时间了,在经历过很多次构建之后,我观察到了一个现象:那就是80%的时间都是webpack花掉的,关键是一些很简单的项目,因为webpack,还是会有一个比较长的安装npm包的时间,那这对于一个图床应用来说,是致命的。

所以我决定摆脱webpack,使用离线版本的vue.min.js来构建应用,将部署时间控制在30s以内,做到提交图片,即刻可用。

<!-- source.html -->

<script src='./libs/js/vue.min.js'></script>
<script src='./libs/js/lodash.chunk.js'></script>
<script src='./libs/js/lodash.throttle.js'></script>
<script src='./libs/js/clipboard.js'></script>
<script src='./index.js'></script>

使用XHR和CustomEvent进行组件化开发

在html顶部引入include.js,改文件的作用是在文档加载完成之后将include标签中的地址通过同步的XHR,请求到组件的html内容,然后写入到页面中。

// include.js

getFileContent: function (url){
    var o = new XMLHttpRequest()

 o.open('get', url, false)
 o.send(null)

 return o.responseText
}

接着通过自定义事件发出通知:

// include.js

var evt = new CustomEvent('included', {
 bubbles: true,
 cancelable: false
})

window.onload = function (){
    new Include().replaceIncludeElements()

    document.dispatchEvent(evt);
}

在其他脚本中接收通知:

// index.js

document.addEventListener('included', function (){...})

通过node预编译组件

仅仅是使用include是不够的,组件的js和css代码同样要分离出来,这样才有意义,于是node出场,其实你理解的webpack,不过时穿上绅士马甲的node编译脚本,本质上还是预编译。

所以不用webpack,我们直溯本源,手写预编译代码。在picpic项目根目录新建一个build文件夹,其中的文件就是预编译要用的代码。

// build/index.js

const fs = require('fs-extra')
const globby = require('globby')
const inject = require('./inject')
const paths = require('./utils/paths')

const main = async () => {
 if (!fs.existsSync(paths.dist)) {
  fs.mkdirSync(paths.dist)
 } else {
  fs.removeSync(paths.dist)
  fs.mkdirSync(paths.dist)
      }

 fs.writeFileSync(`${paths.dist}/index.html`, await inject())
 fs.copySync(paths.assets, paths.dist)
 fs.copySync(paths.getPath('../../src'), paths.dist)
 fs.removeSync(`${paths.dist}/source.html`)

 const less = await globby(`${paths.dist}/**/*.less`)

      less.map(item => fs.removeSync(item))

 console.log('---------- picpic build success! ---------- \n')
}

try {
 main()
} catch (error) {
 console.log('---------- picpic build error! ---------- \n')
 console.error(error)
}

这里的inject就是注入组件和数据之后的html,接下来展示一下如何进行组件注入。

// build/inject/index.js

const fs = require('fs-extra')
const injectData = require('./injectData')
const injectStyles = require('./injectStyles')
const injectTemplates = require('./injectTemplates')
const injectJs = require('./injectJs')
const paths = require('../utils/paths')

function Inject (){
 this.html = ''

 this.getSource = () => {
  this.html = fs.readFileSync(paths.getPath('../../src/source.html')).toString()

  return new Promise(resolve => resolve(this.html))
 }

 this.injectData = async () => {
  this.html = await injectData(this.html)

  return new Promise(resolve => resolve(this.html))
 }

 this.injectStyles = async () => {
  this.html = await injectStyles(this.html)

  return new Promise(resolve => resolve(this.html))
 }

 this.injectTemplates = async () => {
  this.html = await injectTemplates(this.html)

  return new Promise(resolve => resolve(this.html))
 }
}

const inject = async () => {
 return await new Inject()
  .getSource()
  .then(res => injectData(res))
  .then(res => injectStyles(res))
  .then(res => injectTemplates(res))
  .then(res => injectJs(res))
}

module.exports = inject

通过返回this的方法进行链式调用,比一层一层用方法包裹优雅很多,有没有感受到代码之美,嘻嘻。

injectStyles``injectTemplates``injectJs这三种方法异曲同工,原理特简单,就是字符串替换,不过这里要注意空格,少一个都匹配不到。

// build/inject/injectStyles.js

const globby = require('globby')
const paths = require('../utils/paths')

module.exports = async str => {
 const paths_source = await globby([ `${paths.getPath('../../src/components/**/*.css')}` ])
 const paths_target = []

 paths_source.map(item =>
  paths_target.push(item.replace('src', '.').split('/').slice(-4).join('/'))
      )

 const items = paths_target.map(item => '@import ' + "'" + item + "'" + ';' + '\n')

 return str.replace(
  `
      <style></style>
`,
  `
      <style>
            ${items.reduce((total, item) => (total += item), '')}
      </style>
`
 )
}

在页面中,三种占位符分别用于注入组件相关的文件:

<!-- source.html -->

<!-- 注入样式导入代码 -->
<style></style>

<!-- 注入模版导入代码 -->
<template-slot></template-slot>

<!-- 注入脚本导入代码 -->
<script id="component_scripts"></script>

注入之后的结果为:

<!-- dist/index.html -->

<!-- 注入样式导入代码 -->
<style>
@import './components/Detail/index.css';
@import './components/Empty/index.css';
@import './components/FolderSelect/index.css';
@import './components/Header/index.css';
@import './components/ImgItems/index.css';
@import './components/Msg/index.css';
@import './components/Pagination/index.css';
</style>

<!-- 注入模版导入代码 -->
<include src="./components/Detail/index.html"></include>
<include src="./components/Empty/index.html"></include>
<include src="./components/FolderSelect/index.html"></include>
<include src="./components/Header/index.html"></include>
<include src="./components/ImgItems/index.html"></include>
<include src="./components/Msg/index.html"></include>
<include src="./components/Pagination/index.html"></include>

<!-- 注入脚本导入代码 -->
<script src="./components/Detail/index.js"></script>
<script src="./components/Empty/index.js"></script>
<script src="./components/FolderSelect/index.js"></script>
<script src="./components/Header/index.js"></script>
<script src="./components/ImgItems/index.js"></script>
<script src="./components/Msg/index.js"></script>
<script src="./components/Pagination/index.js"></script>

不要诟病组件文件夹大写,我是react的拥趸,如果不是因为web-component强制使用-分割符小写,所有的组件我都希望大写,因为辨识度比前者高很多。

通过node预编译目录数据

主要是通过dree到处树形数据,通过imageinfo获取图片长宽,然后再进行数据裁剪,把需要的数据进行组装后导出。代码多且杂,这里仅结果,有兴趣的可以去github看代码。

{
    "name":"assets",
    "type":"directory",
    "size":"1.14MB",
    "children":[
        {
            "name":"projects",
            "type":"directory",
            "size":"1.14MB",
            "children":[
                {
                    "name":"picpic",
                    "type":"directory",
                    "size":"1.14MB",
                    "children":[
                        {
                            "name":"choose_gh_pages.jpg",
                            "type":"file",
                            "extension":"jpg",
                            "size":"61.1KB",
                            "dimension":"2020x940",
                            "path":"projects/picpic/choose_gh_pages.jpg"
                        },
                        {
                            "name":"folder_hover_status.jpg",
                            "type":"file",
                            "extension":"jpg",
                            "size":"116.74KB",
                            "dimension":"956x1896",
                            "path":"projects/picpic/folder_hover_status.jpg"
                        }
                    ]
                }
            ]
        }
    ]
}

然后写入到html中:

// build/inject/injectData.js

const { getFileTree } = require('../utils')

module.exports = async str => {
 const tree = await getFileTree()

 return str.replace(
  `
      <head>
            <title>PicPic</title>
      </head>
`,
  `
      <head>
            <title>PicPic</title>
            <script>
                  window.img_paths=${JSON.stringify(tree)}
            </script>
      </head>
`
 )
}

做成命令行工具

仅仅做成上面那样使用起来,还需要别人clone你的仓库,后续升级麻烦,而且编译源文件什么的都暴露出来了,看起来脏的不行,所以不仅要产品本身美,使用方式也需要简单优雅。

package.json 中添加如下字段,发布包之后,当别人在 npm i @matrixage/picpic 时会生成命令行工具文件:

"bin": {
    "picpic": "./bin/index.js"
}

编写命令行工具代码:

// bin/index.js

#!/usr/bin/env node

const fs = require('fs-extra')
const path = require('path')
const child_process = require('child_process')
const pkg = require(`${process.cwd()}/package.json`)

const main = () => {
 const args = process.argv[2]
 const root = process.cwd()
 const getPath = p => path.join(__dirname, p)

 switch (args) {
  case 'init':
   pkg['scripts']['build'] = 'picpic build'

   fs.writeFileSync('./package.json', JSON.stringify(pkg, null, 2).concat('\n'))
   if (!fs.existsSync(`${root}/assets`)) fs.mkdirSync(`${root}/assets`)
   if (!fs.existsSync(`${root}/.github`)) fs.mkdirSync(`${root}/.github`)
   if (!fs.existsSync(`${root}/.gitignore`)) fs.writeFileSync(`${root}/.gitignore`,`/dist \n/node_modules \n.DS_Store`)
   fs.copySync(getPath('../.github'), `${root}/.github`)

   console.log('---------- picpic init success! ---------- \n')
   break
  case 'build':
   child_process.execSync(`node ${getPath('../build/index.js')}`)
   break
  default:
   break
 }
}

try {
 main()

 process.exit(0)
} catch (e) {
 console.error(e)

 process.exit(1)
}

当用户 npm i @matrixage/picpic 之后,在 package.jsonscripts 字段中加入 "init": "picpic init" ,然后执行npm run init,项目根目录会生成 .github``assets 文件夹以及 .gitignore 文件。

这个时候用户只需要把图片移动到assets文件夹中,支持在assets中新建任意不超过12层的文件夹。然后提交到github,github action将自动进行构建,然后把构建出的dist文件夹推送到仓库的gh-pages上,如果没有开启gh-pages请自行开启。

至此,全部构建流程讲解完毕。这个过程,写预编译代码其实是最简单,麻烦的是:

  • 如何构建美的应用?
  • 如何让用户简单且优雅地使用?

回首我做过的所有项目,花在逻辑上的时间其实是最少的,写逻辑是跟机器对话,机器嘛,就那几句话,记住就行了。而画界面,做交互,是在跟人,首先就是跟自己进行对话,了解自己内心深处的想法,然后就是跟用户进行对话,其实你把用户当成千千万万个我,那你就能感受到,你的idea,该如何生长,你的画,该是何模样。

总之,以人为本。

DEMO地址:https://matrixage.github.io/picpic_example/

项目地址:https://github.com/MatrixAges/picpic

注意,在github的readme文件中使用username.github.io/repo/~这样的链接,github会将之自动转化为camo.githubusercontent.com该host下的图片链接,该链接被DNS污染了,如要预览,请在host中加入如下DNS解析:

199.232.96.133 raw.githubusercontent.com
199.232.96.133 camo.githubusercontent.com

如果你发现访问github很慢,那是因为本地服务商在进行DNS网络过滤,加入如下host跳过服务商网络过滤:

140.82.112.3 github.com

如果你的仓库的主分支是master而不是main,请自行修改构建脚本依赖分支为master,在.github/workflows/ci.yml中。

本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/R4PtwesR-LQS65Y301v0xw

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 相关文章
Android插件化方案 5年以前  |  237231次阅读
vscode超好用的代码书签插件Bookmarks 2年以前  |  8065次阅读
 目录