The cause of

Speaking of graph bed application, there are many things on Github, but most of them are based on some cloud vendors free static storage services to achieve, such as qiuniuyun static storage, considering the desire of these cloud vendors to make money, so I do not trust them as graph bed service providers.

Github is also supported, such as Picgo, but when it comes to Personal tokens, I’m not comfortable writing my tokens into a desktop application for an open source project. Moreover, the github image link exported by Picgo is the host link of Githubusercontent.com. As is known to all, this domain name is contaminated by DNS in many areas of China, and can only be accessed by changing host or scientific Internet, so the conclusion is that, Picgo based on github export picture link, in China is useless.

Is there a way to ensure that the image link does not get corrupted by DNS or be removed from the wall without affecting the developer’s Personal token?

So, picpic. Picpic is I’m doing a another big open source projects take time to realize, in the process of initial version only took two days to write out, but I think is a qualified and good product maker, is not willing to output a use trival, functional disability semi-finished products to others use – the key is for my own use.

The core point of my product is whether I am willing to use what I have made, whether I can feel the “beauty” when I use it, and whether I can feel it calmly in the product, which is very important. It is because I have never positioned myself as a front-end or node development, but as a product maker, and the ultimate ideal is artist. It’s making art with the idea that you’re not writing code, you’re painting a picture, you’re enjoying the process, and if someone else can enjoy the “result,” that’s great.

So here it is:

The DEMO address: matrixage. Making. IO/picpic_exam…

Project address: github.com/MatrixAges/…

Based on the offline version, the single page application is built without the vue.js of Webpack. The principle is to precompile the image data through Node and write it into the window object, and then fragment it through chunk to provide page-turning function. As for folder mode, It is used to precompile the file structure under assets folder into tree data through Node, write it into window object, and then call JS in the page.

The service is based on Github Pages and is automatically built using Github Actions. Through automated build deployment, static files are deployed to the GH-Pages branch. IO /${repo}/${img_path} to access unshot static images.

After a few polishing, I finally made it into cli, you just need NPM i@matrixage /picpic, and you can use it.

Here’s how I built such a single-page application using Node and Vue.

Web applications without Webpack

I’ve been using Github Actions for a while now, and after many builds, I’ve observed one thing: That is, 80% of the time is spent by Webpack. The key is some very simple projects, because webpack still has a long time to install the NPM package, which is fatal for a graph bed application.

So I decided to get rid of Webpack and use the offline version of vue.min.js to build the application, keeping the deployment time under 30 seconds, and making sure that images are submitted and immediately available.

<! -- 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>
Copy the code

Componentized development using XHR and CustomEvent

Add include.js to the top of the HTML file. After the document is loaded, the address in the include tag is requested to the HTML content of the component through the synchronous XHR, and then written to the page.

// include.js

getFileContent: function (url){
    var o = new XMLHttpRequest()
	
	o.open('get', url, false)
	o.send(null)
	
	return o.responseText
}
Copy the code

Notification is then issued via custom events:

// include.js

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

window.onload = function (){
    new Include().replaceIncludeElements()
    
    document.dispatchEvent(evt);
}
Copy the code

Receive notifications in other scripts:

// index.js

document.addEventListener('included'.function (){... })Copy the code

Precompile components through Node

Simply using include is not enough, component JS and CSS code also need to be separated to make sense, so Node, in fact you understand webpack, but put on a gentleman’s vest node compile script, is essentially pre-compiled.

So instead of webpack, we go straight back to the source and hand-write pre-compiled code. Create a new build folder in the picpic project root directory, where the files will be used to precompile the code.

// 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)
}
Copy the code

Inject is HTML after injecting components and data. Here we show how to inject components.

// 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
Copy the code

Chain calls by returning this are much more elegant than wrapping methods layer by layer. Do you feel the beauty of code?

InjectStyles injectTemplates injectJs, injectJs, injectJs, injectJs, injectJs, injectJs, injectJs, injectJs, injectJs, injectJs, 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>
            ${items.reduce((total, item) => (total += item), ' ')}
      </style>
`)}Copy the code

In a page, three placeholders are used to inject component-related files:

<! -- source.html -->

<! Insert style import code -->
<style></style>

<! Import code into template -->
<template-slot></template-slot>

<! Insert script into code -->
<script id="component_scripts"></script>
Copy the code

The result after injection is:

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

<! Insert style import code -->
<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>

<! Import code into template -->
<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>

<! Insert script into code -->
<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>
Copy the code

Don’t criticize component folder capitalizations. I’m a react fan, and if it weren’t for the web-component’s mandatory lowercase – separator, I’d capitalize all components because they’re much more recognizable.

Precompile directory data through Node

Mainly through DREE tree data everywhere, through imageInfo to obtain the length and width of the picture, and then carry out data clipping, the need for data assembly after export. The code is numerous and miscellaneous, here is only the result, interested can go to Github to see the code.

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

Then write to HTML:

// build/inject/injectData.js

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

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

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

Make it a command-line tool

Just make the above use up, also need others to clone your warehouse, the subsequent upgrade trouble, and compile the source files of what are exposed, look dirty, so not only to the product itself beauty, use also need simple elegant.

Add the following fields to package.json to generate command line utility files when others are in NPM i@matrixage /picpic after publishing the package:

"bin": {
    "picpic": "./bin/index.js"
}
Copy the code

Write command line tool code:

// 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)}Copy the code

After user NPM i@matrixage /picpic, add “init” to package.json’s scripts field: “Picpic init”, then execute NPM run init, the project root directory will generate.github Assets folder and.gitignore file.

In this case, the user only needs to move the picture to the assets folder. You can create any folder with no more than 12 layers in assets. Then submit to Github, Github Action will automatically build, and then push the built dist folder to gh-Pages of the warehouse. If gh-Pages is not enabled, please open it yourself.

At this point, the whole construction process is explained. This process, writing precompiled code is actually the easiest, the trouble is:

  • How to build beautiful apps?
  • How to make it simple and elegant for users?

Looking back on all the projects I’ve ever done, I’ve spent the least amount of time on logic. Writing logic is talking to a machine. A machine, it’s just a few words. And drawing interface, doing interaction, is talking with people, first is to have a dialogue with yourself, to understand their own thoughts, and then is to have a dialogue with the user, in fact, you take the user as thousands of me, then you can feel, your idea, how to grow, your painting, what is the appearance.

In a word, put people first.

The DEMO address: matrixage. Making. IO/picpic_exam…

Project address: github.com/MatrixAges/…

Note that in making the readme file. Use the username lot. IO/repo / ~ such links, making will automatically be converted into camo.githubusercontent.com under the host image links, the link was contaminated with DNS, To preview, add the following DNS resolution to host:

199.232.96.133 raw.githubusercontent.com
199.232.96.133 camo.githubusercontent.com
Copy the code

If you find that accessing Github is slow, it is because your local service provider is doing DNS filtering.

140.82.112.3 github.com
Copy the code

If the main branch of your repository is Master instead of main, modify the build script to rely on the master branch in.github/workflows/ci.yml.