开发一个npm包并发布

7/24/2021 npmfiles-preview

先简单的介绍一下今天的主角:

# files-preview 是什么?

前不久配合一位朋友魔改了 http-server。做了一个增强对 Measure 导出的项目的管理的 npm 包。

npm files-preview (opens new window) gitee files-preview (opens new window)

http-server 已有成熟的流程和配置,所以再一次站在巨人的肩膀上美化了一下 GUI

改造前:

改造后:

# 为什么而做?

目前设计和开发协作上,主要有以下几种模式:

  1. 使用在线协作平台,如蓝湖、摹客、CoDesign 和 Zeplin 等;
  2. 导出离线标注文件,例如 Sketch 的 Measure 和 Figma 的 Heron Handoff ;
  3. 使用 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
}
1
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)
  }
}
1
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)
1
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)
    })
  })
}
1
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}}'
1

如何使用 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"
  }
}
1
2
3
4
5
6
7
8
9
10

name 便是项目的名称了 version 就是每次发布版本的时候的版本号,这个版本号是不能重复的 剩下的就是描述,和源码仓库地址,在 npm 上都会体现出来。

  • 本地添加你的 npm 帐号
npm adduser
1

执行后根据提示,输入对应的内容就算登陆成功了~

  • 进入到要发布的项目的 package.json 目录中去执行
npm published
1

# 使用

先使用 npm install files-preview -g 全局安装一下。然后执行 files-preview就行。

然后就可以在任意目录执行命令,并以更有好的 GUI 显示本地的文件了~。更多的功能探索可以详细看下 npm 上的描述。最好还能来个star

Last Updated: 8/13/2021, 11:27:09 PM