开发一个npm包并发布
先简单的介绍一下今天的主角:
# files-preview 是什么?
前不久配合一位朋友魔改了 http-server
。做了一个增强对 Measure 导出的项目的管理的 npm 包。
npm files-preview (opens new window) gitee files-preview (opens new window)
http-server 已有成熟的流程和配置,所以再一次站在巨人的肩膀上美化了一下 GUI
改造前:
改造后:
# 为什么而做?
目前设计和开发协作上,主要有以下几种模式:
- 使用在线协作平台,如蓝湖、摹客、CoDesign 和 Zeplin 等;
- 导出离线标注文件,例如 Sketch 的 Measure 和 Figma 的 Heron Handoff ;
- 使用 Figma 或类似的新设计工具(最近国内上线了不少);
开发该 npm 包是对第 2 个场景的一个扩展。
# 增加「项目」类型
文件夹新增「项目」类型,可通过在 assets 文件夹(使用 Measure 插件会自动生成一个,也能手动添加)中增加 cover.png 来使该文件夹成为「项目」。
# 图片及 svg 预览
支持图片以及 svg 文件的预览,方便浏览和查找所需的图片文件。
# 改造的开始
从启动后的页面不难看出 http-server 也是基于 ecstatic
来进行渲染目录的。
并且有一段核心的代码:http-server 重写了 path.normalize
方法。这对后面的流程进行非常的重要,否则 ecstatic 根本就不识别根目录
var _pathNormalize = path.normalize
path.normalize = function(p) {
var caller = getCaller()
var result = _pathNormalize(p)
// https://github.com/jfhbrook/node-ecstatic/blob/master/lib/ecstatic.js#L20
if (caller === 'decodePathname') {
result = result.replace(/\\/g, '/')
}
return result
}
2
3
4
5
6
7
8
9
10
与此同时,把从 node_modules 引入的改为相对路径引入。进行 ecstatic 的修改
# ecstatic 改造
调试过程就不细说了,不过看了一下代码,非常多值得学习的地方。最终直接找到渲染的方法
/ecstatic/show-dir/index.js (opens new window)
- render 函数
function render(dirs, renderFiles, lolwuts) {
// each entry in the array is a [name, stat] tuple
let html = `${[
'<!doctype html>',
'<html>',
' <head>',
' <meta charset="utf-8">',
' <meta name="viewport" content="width=device-width">',
` <title>Index of ${he.encode(pathname)}</title>`,
` <style type="text/css">${css}</style>`,
' </head>',
' <body>',
`<h1>Index of ${he.encode(pathname)}</h1>`
].join('\n')}\n`
html += '<table>'
const failed = false
const writeRow = file => {
// render a row given a [name, stat] tuple
const isDir = file[1].isDirectory && file[1].isDirectory()
let href = `${parsed.pathname.replace(/\/$/, '')}/${encodeURIComponent(file[0])}`
// append trailing slash and query for dir entry
if (isDir) {
href += `/${he.encode(parsed.search ? parsed.search : '')}`
}
const displayName = he.encode(file[0]) + (isDir ? '/' : '')
const ext = file[0].split('.').pop()
const classForNonDir = supportedIcons[ext] ? ext : '_page'
const iconClass = `icon-${isDir ? '_blank' : classForNonDir}`
// TODO: use stylessheets?
html += `${'<tr>' + '<td><i class="icon '}${iconClass}"></i></td>`
if (!hidePermissions) {
html += `<td class="perms"><code>(${permsToString(file[1])})</code></td>`
}
html +=
`<td class="file-size"><code>${sizeToString(file[1], humanReadable, si)}</code></td>` +
`<td class="display-name"><a href="${href}">${displayName}</a></td>` +
'</tr>\n'
}
dirs.sort((a, b) => a[0].toString().localeCompare(b[0].toString())).forEach(writeRow)
renderFiles.sort((a, b) => a.toString().localeCompare(b.toString())).forEach(writeRow)
lolwuts.sort((a, b) => a[0].toString().localeCompare(b[0].toString())).forEach(writeRow)
html += '</table>\n'
html +=
`<br><address>Node.js
${process.version}
/ <a href="https://github.com/jfhbrook/node-ecstatic">ecstatic</a> ` +
`server running @
${he.encode(req.headers.host || '')}
</address>\n` +
'</body></html>'
if (!failed) {
res.writeHead(200, { 'Content-Type': 'text/html' })
res.end(html)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
可以看到 ecstatic 是动态拼了一个 html 结构出来。如果请求的路径为目录则执行下面的代码,输出拼接的结构,否则就输出源文件。
res.writeHead(200, { 'Content-Type': 'text/html' })
res.end(html)
2
# 改用 vue.js + less 增强维护性
既然最后只要输出 html 代码,那我直接用一个 .html
文件把逻辑写好,最后使用 node
把文件读进来渲染不就可以了?说干就干!
最后函数改成了那么短
function render(dirs, renderFiles, lolwuts) {
// each entry in the array is a [name, stat] tuple
let datas = { dirs, renderFiles, lolwuts, current: pathname }
fs.readFile(path.resolve(__dirname, 'template/index.html'), 'utf8', (err, data) => {
if (err) {
return
}
data = data.replace(/'{{treeData}}'/, JSON.stringify(datas))
fs.readFile(path.resolve(__dirname, 'template/index.css'), 'utf8', (cssError, cssData) => {
if (cssData) {
data = data.replace('<style src="./index.css"></style>', `<style type="text/css">${cssData}</style>`)
}
res.writeHead(200, { 'Content-Type': 'text/html' })
res.end(data)
})
})
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
改用外部 html 最大的一个问题就是数据该如何渲染?
在上面第 11 行代码中,有一个正则,匹配 替换为要渲染的数据
相应的在对应的 html 文件中有一段 JS,来接收这个值,那剩下的就是 JS 发光发热了
var treeData = '{{treeData}}'
如何使用 less 来开发?
和 treeData 技巧同理,使用正则的方式,把写好的 css 读进来,然后替换到 style
标签里面去
上面插入的也是 index.css
文件的内容,并不是 less。因为 less
只用作本地开发,配合 vscode 的 easy-less
插件,保存后自动生成 css。使用 less 开发体验感会好很多,包括后续考虑更换皮肤,css 变量等都有好处~
其他的资源,比如 vue.js
因为 http-server 启动的是本地服务,而且并不知道最终启动的根目录是在哪里,所以在 html 中并不存在使用 相对路径
引入资源这一说法
想要引入资源,必须提前写入 html/加载外网的内容,比如 vue.js 就是加载的 cdn 的服务。
毕竟不是什么黑科技技术,感兴趣的可以到项目仓库看下源码.顺便给个 star~ show-dir/template (opens new window)
# 发布到 npm 中
- 先到 npm 注册一个帐号
- 修改项目的 package.json 文件
{
"name": "files-preview",
"version": "0.0.12",
"description": "基于 http-server 增强对 Measure 导出的项目的管理",
"main": "./lib/files-preview",
"repository": {
"type": "git",
"url": "https://gitee.com/Jioho/files-preview"
}
}
2
3
4
5
6
7
8
9
10
name 便是项目的名称了 version 就是每次发布版本的时候的版本号,这个版本号是不能重复的 剩下的就是描述,和源码仓库地址,在 npm 上都会体现出来。
- 本地添加你的 npm 帐号
npm adduser
执行后根据提示,输入对应的内容就算登陆成功了~
- 进入到要发布的项目的 package.json 目录中去执行
npm published
# 使用
先使用 npm install files-preview -g
全局安装一下。然后执行 files-preview
就行。
然后就可以在任意目录执行命令,并以更有好的 GUI 显示本地的文件了~。更多的功能探索可以详细看下 npm 上的描述。最好还能来个star