使用npm来创建JavaScript图标库

557 阅读5分钟

简介

在这篇文章中,我们将学习如何使用Node Package Manager(npm)创建一个JavaScript图标库。要开始学习,请确保你有以下条件。

  • Node v14(nvm,即Node版本管理器,可以用来指定每个工作目录的Node版本)
  • 一个npm账户
  • 一个Figma账户

初始设置

在命令行中为你的包创建一个目录,其中$package_name 是你的实际包名。

$ mkdir $package_name && cd $package_name

在确定了你的包的名字之后,你需要知道它是否要被范围化(即package@username/package )。在你的过程中尽早找到一个名字并注册是非常重要的,因为名字只能使用一次,其他开发者可能会抢先一步。

要找到一个名称是否可用,你可以使用这个方便的npm名称检查器,或者你可以在浏览器中输入 [https://www.npmjs.com/package/](https://www.npmjs.com/package/)到你的浏览器中,然后像这样把你选择的名称附加到URL的末尾。

https://www.npmjs.com/package/iconslib

如果这个名字是可用的,你将会看到一个404页面。如果你怀疑这个包是为将来使用而注册的,你可以要求npm给你这个名字。

运行$ npm init 。在你想要的软件包名称不可用的情况下,你可以通过运行$ npm init --scope=$your_username ,用你的用户名来定位该软件包。这将提示你填写一些常见的npm属性并创建一个package.json ,我们将在整个文章中继续填写。请确保将版本设置为0.0.0

你的package.json 应该看起来像这样。

{
  "name": "$package_name",
  "version": "0.0.0",
  "description": "$package_description",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "$your_username <$your_email> ($your_website)",
  "license": "$license"
}

选择一个npm许可证

选择一个合适的许可证对你构建的每个npm包都是至关重要的,因为如果没有许可证,许多个人和组织会放弃使用你的包。

请访问Choose a License来选择一个。复制文本并相应地替换[year][fullname] ,然后将内容粘贴到你的包目录根部的一个license 文件中。

如果你做了修改,别忘了更新package.json 中的license 属性。

设置Git和GitHub

从自动更新过时的依赖关系到运行测试和标记你的版本,GitHub将在维护你的npm包中发挥巨大作用。

要开始工作,请在软件包目录下运行$ git init 来初始化一个新的 Git 仓库。

然后,创建一个带有简短描述的README.md 文件,你可以在以后对其进行扩展,以及一个包含以下内容的.gitignore 文件。

# build output

# dependencies
/node_modules

# build
/outline
/solid

# misc
.DS_Store
.npm
.eslintcache
.yarn-integrity

# env files
.env

# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Output of 'npm pack'
*.tgz
/package

之后,访问 [https://github.new](https://github.new)并创建一个资源库。

在添加remote 或推送任何修改到这个新仓库之前,你首先要修改你的package.json ,添加repository,bugs, 和homepage 属性。你也可以添加keywords 属性,使你的软件包更容易被发现。

{
  "homepage": "https://github.com/$your_username/$github_repo#readme",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/$your_username/$github_repo.git"
  },
  "bugs": {
    "url": "https://github.com/$your_username/$github_repo/issues"
  },
  "keywords": [
    "icons",
    "svg",
    "react"
  ]
}

如果你打算为你的图标库建立一个网站,你可以将homepage 的值设置为你的域名。主页可以通过$ npm home <package>

然后你就可以去把你现有的仓库推送到GitHub。

$ git add . && git commit -m 'initial commit'
$ git remote add origin git@github.com:$your_username/$github_repo.git
$ git branch -M main
$ git push -u origin main

提前发布到 npm

如前所述,尽早发布到npm可以保护你的软件包名称不被他人占用。要发布到 npm,请创建一个账户,并在软件包目录根部的命令行上认证自己。

$ npm login

这将提示你输入你的凭证。之后,你就可以用以下命令发布到npm注册表。

$ npm publish --access public

这将使用你在package.json 中定义的name 属性创建一个公共npm包。

如果你想取消发布你的包,运行$ npm unpublish $package_name -f 。理想情况下,这是在发布后的72小时内完成的,但如果需要的话,npm对在该时间段后的解压有一些额外的指导。

一旦发布,你的软件包就会存在于 [https://www.npmjs.com/package/$package_name](https://www.npmjs.com/package/$package_name).

创建图标

SVG规范的灵活性使得图标设计者可以使用规范的不同功能以多种方式实现相同的视觉效果,比如使用path 与基本形状,或者fillstroke

然而,在创建一个图标集或库时,必须做出一些假设,以便更好地进行优化,因为你必须尽可能地分发经过优化的SVG文件。

轮廓与实体图标

你可以很容易地做出一个假设,就是用strokes来表示轮廓图标,用fills来表示实体图标。实体图标往往是在可识别性很重要的情况下使用的,所以fills可以很好地满足这一目的。Strokes很适合于创建微小的细节,所以它们与轮廓图标一起使用。

这是一个有用的经验法则,便于优化,因为你将有一个使用strokes编写的大纲图标子集,另一个使用fills。你将避免有个别图标同时使用strokes和fills编写的噩梦,这将是无法在关键方面进行优化。

请注意,如果你要创建一个双色调的图标集,你将不得不使用不同的策略。如果你想进一步了解创建图标的最佳做法,谷歌的系统图标指南是一个不错的资源。

使用Figma作为设计工具

SVG规范非常庞大,而且供应商(包括编辑器和浏览器)只实现了其中一部分。通过将 SVG 导出和导入到不同的环境中,您会不断地 "破坏 "您的 SVG。

如果你用Figma工作,你可以用钢笔从预先绘制的矢量中分支出来,并创建铣削接头。不幸的是,这在浏览器中还不能以同样的方式实现。

Using Figma as a design tool

Figma 中可导出的图标

为了能够在以后的 npm 软件包构建过程中以编程方式从 Figma 导出图标,您需要从您的图标中创建 Figma 组件,并将它们分成outlinesolid 两个页面。

Outline icons created using stroke in Figma

使用笔画创建的轮廓图标

Solid icons creating using fill in Figma

使用填充创建的实体图标

创建一个构建过程

程序化输出

完成后,使用figma-export软件包套件,从 Figma 自动导出您的图标。您需要一个个人访问令牌,该令牌可从您的Figma 账户设置中获得,同时还需要您的 Figma 文件的 ID。

Using Figma as a design tool

这个URL的高亮部分就是您的文件ID。

Figma file ID highlighted in URL

为了快速测试我们的 Figma 图标,我们可以运行以下npx 命令。

$ FIGMA_TOKEN=180901-363fe5d2-f0c2-45a9-b564-d49c708281ea npx -p @figma-export/cli -p @figma-export/output-components-as-svg figma-export components V7WbFH5FKFahwtiqWdxzoO -O @figma-export/output-components-as-svg

FIGMA_TOKEN's value 和V7WbFH5FKFahwtiqWdxzoO 分别替换为您的 token 和文件 ID。

该命令应该创建一个output 目录,其中包含以下内容。

output
├── outline
│   ├── Circle.svg
│   ├── Square.svg
│   └── S.svg
└── solid
    ├── Circle.svg
    ├── Square.svg
    └── S.svg

如果导出成功,我们可以继续创建npm包。

在你的npm包中安装依赖项

回到我们的包目录,安装dotenv,@figma-export/cli, 和@figma-export/output-components-as-svg 作为 dev 的依赖项。

$ npm install --save-dev dotenv @figma-export/cli @figma-export/output-components-as-svg

在一个.env 文件中,将FIGMA_TOKEN 环境变量包括在其中。

# .env
FIGMA_TOKEN=180901-363fe5d2-f0c2-45a9-b564-d49c708281ea # use your own
FILE_ID=V7WbFH5FKFahwtiqWdxzoO

dotenv 将被用来在构建过程中加载我们的变量。

配置figma-export

在根目录下创建一个figma.config.js 文件,然后进行以下步骤。

  • 要求dotenv'sconfig 函数,该函数读取.env ,并将你的变量塞入process.env
  • 定义一个fileId 常量,用于保存您的 Figma 文件 ID 环境变量。
  • figma-export的输出目录定义一个outputters 常量。
  • 导出一个类型为FigmaExportRc 的对象,这有助于自动完成,并从solidoutline Figma 页面中提取。
// figma.config.js
require("dotenv").config();const fileId = process.env.FILE_ID;
const outputters = [
require("@figma-export/output-components-as-svg")({ output: "./" })
];/** @type {import('@figma-export/types').FigmaExportRC} */
module.exports = {
commands: [
[ "components", {
fileId,
onlyFromPages: ["solid"],
outputters,
},
],
[ "components", {
fileId,
onlyFromPages: ["outline"],
outputters,
},
],
],
};

要测试这是否有效,请运行$ npx figma-export use-config figma.config.js ,它应该创建单独的solidoutline 目录来存放我们的图标。

优化图标

为了优化,我们将使用无处不在的 svgo.SVGO会根据我们配置的插件集来优化我们的SVG。你可以使用这个SVGO前端工具在浏览器中直接测试这些规则的子集。

至于优化,我们将应用默认的配置,并在我们认为合适的地方进行偏离。

removeDimensions

通过删除widthheight ,转而使用viewBox ,你可以避免依赖默认大小。这有助于识别导出的不好的SVG,设计工具在SVG的尺寸上增加了一两个像素--例如,将一个图标从100px 改为101px 。依靠默认尺寸会使SVG看起来很清晰,但手动将预期尺寸重置为100px ,会使SVG看起来稍微模糊,并提示你重新正确导出。

removeAttrsaddAttributesToSVGElement

我们之所以将solidoutline 变体优化分割开来,是为了让我们只出现一次特定的属性。removeAttrs 将允许我们从SVG中完全删除我们选择的一个属性,而addAttributesToSVGElement 将允许我们将其添加回svg 元素本身。

<!-- before -->
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
  <path stroke="#000" d="M.5.5h15v15H.5V.5z"/>
  <path stroke="#000" d="M6 10V6h4v4H6z"/>
</svg>

<!-- after -->
<svg stroke="#000" fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
  <path d="M.5.5h15v15H.5V.5z"/>
  <path d="M6 10V6h4v4H6z"/>
</svg>

设置currentColor

currentColor 被广泛认为是第一个CSS变量。在CSS中设置一个颜色值到currentColor (border: 2px dashed currentColor ,例如)将自动和反应性地选择该元素的color 属性值。

在我们的SVG中,我们可以用currentColor ,预先填入fillstroke 属性,以方便用户覆盖图标的颜色。

请注意,当SVG被用于<img src="our.svg" /> ,用于浏览器缓存时,currentColor 会简单地将SVG渲染成黑色。要改变这种情况下的颜色,可以利用SVG的feColorMatrix过滤器

另外,请注意,outline SVG中的fill="none" ,需要保持原样。

优化SVG

为了使用svgo ,我们可以直接安装并运行它来对付我们导出的SVG。但由于我们已经在使用figma-export ,它将svgo 整合为transformer ,所以我们将使用它来代替。

$ npm i --save-dev @figma-export/transform-svg-with-svgo

在默认配置的同时,我们将添加这些优化,其中sortAttrs 是一种风格上的选择。

// figma.config.js
require("dotenv").config();
const svgo = require('@figma-export/transform-svg-with-svgo')

const fileId = process.env.FILE_ID;
const outputters = [
  require("@figma-export/output-components-as-svg")({ output: "./" })
];

/** @type {import('svgo').PluginConfig[]} */
const solidSVGOConfig = [
  { removeDimensions: true },
  { sortAttrs: true },
  { removeAttrs: { attrs: "fill" } },
  { addAttributesToSVGElement: { attribute: { fill: "currentColor" } } },
];

/** @type {import('svgo').PluginConfig[]} */
const outlineSVGOConfig = [
  { removeDimensions: true },
  { sortAttrs: true },
  { removeAttrs: { attrs: "stroke" } },
  { addAttributesToSVGElement: { attribute: { stroke: "currentColor" } } },
];

/** @type {import('@figma-export/types').FigmaExportRC} */
module.exports = {
  commands: [
    ["components", {
        fileId,
        onlyFromPages: ["solid"],
        transformers: [svgo({ multipass: true, plugins: solidSVGOConfig })],
        outputters,
      },
    ],
    ["components", {
        fileId,
        onlyFromPages: ["outline"],
        transformers: [svgo({ multipass: true, plugins: outlineSVGOConfig })],
        outputters,
      },
    ],
  ],
};

最后,我们将为我们的图标创建命令添加一个figma npm脚本。

{
  "scripts": {
    "export": "figma-export use-config figma.config.js"
  }
}

运行$ npm run export 将输出我们的outlinesolid 目录。

包装你的图标

要发布你的图标,我们必须首先确定发布的内容。为此,npm的package.json 使用了 files属性。当你没有给它一个值时,npm会发布所有不包括在.gitignore 中的东西。对于我们的目的来说,outlinesolid 是所有需要发布的内容,所以我们会在package.json 中添加一个files 数组作为。

{
  "files": [
    "outline/",
    "solid/"
  ]
}

注意,README.md,package.json, 和LICENSE 都是发布的,而不考虑设置。有了这个变化,我们现在可以测试和发布我们的包了。

测试和发布我们的npm包

为了模拟发布我们的npm包,我们可以使用npm pack 命令。运行npm pack 将创建一个以你的包的名称和版本命名的TAR文件。我们可以解压这个TAR文件来测试我们的包。

$ tar zxvf $package_name-0.0.0.tgz

这将输出一个package 目录,其中有我们发布的文件。

非常重要的一点是,如果你在运行npm run export 后删除或修改outlinesolid 目录,那么这些修改将被发布为npm pack 所示。出于这个原因,我们应该设置一个npm脚本来清理、导出和发布软件包,以消除任何干扰的机会。

清理和构建脚本

为了清理我们的工作空间,我们需要删除outline,solid, 和package 目录以及任何 TAR 文件,尽管package 和 tar 文件被排除在发布之外。

我们可以借助于$ rm -rf 来删除这些目录和文件,但是rm 并不是跨平台的。另一个选择是安装 rimraf并使用它来代替。

$ npm i --save-dev rimraf


{
  "scripts": {
    "clean": "rimraf outline solid package *.tgz",
    "export": "figma-export use-config figma.config.js"
  }
}

为了构建我们的软件包,我们将使用npm-run-all 包连续运行脚本cleanexport ,该包暴露了一个我们可以使用的脚本run-s

$ npm i --save-dev npm-run-all


{
  "scripts": {
    "clean": "rimraf outline solid package *.tgz",
    "export": "figma-export use-config figma.config.js",
    "build": "run-s clean export"
  }
}

运行npm run build 现在将运行我们的cleanexport 脚本。

测试

从 Figma 自动导出图标意味着我们现在可以编写一个简单的测试脚本,以确保我们的图标被正确导出。要做到这一点,我们可以安装cheerio ,一个标记分析器,以验证我们的viewBox 属性。

$ npm i --save-dev cheerio

为了测试我们的图标,我们将创建一个test.js ,其中包含以下内容。

const fs = require("fs");
const $ = require("cheerio");

const directories = ["outline", "solid"];
let errors = 0;

directories.forEach((dir) =>
  fs.readdirSync(dir).forEach((file) => {
    const viewBox = $.load(fs.readFileSync(`${dir}/${file}`))("svg").attr(
      "viewBox"
    );
    if (viewBox !== "0 0 16 16") {
      console.error(
        `Error: \`${dir}/${file}\` has a viewBox of \x1b[31m\`${viewBox}\`\x1b[0m`
      );
      errors++;
    }
  })
);

if (errors > 0) {
  process.exit(1);
} else {
  console.log("Tests passed!");
}

这将从outlinesolid 目录中读取所有的SVG,并在它们上面循环,将它们的viewBox 属性与0 0 16 16 进行比较。如果比较失败,我们会显示一个有用的信息,并以1 退出代码退出节点进程,表明我们的测试脚本已经失败。

在我们的package.json ,我们应该更新我们的脚本,添加一个test 脚本。

{
  "scripts": {
    "clean": "rimraf outline solid package *.tgz",
    "export": "figma-export use-config figma.config.js",
    "test": "node test.js",
    "build": "run-s clean export"
  }
}

npm tnpm run test 的缩写,我们可以用它来手动运行我们的测试脚本。

发布我们的图标

向npm发布新版本需要运行测试,提升软件包的版本,并添加一个Git标签。

语义版本管理

为了提升版本,npm通过它的npm version 命令使用了语义版本管理,或称semver

semver 是最流行的API版本格式之一。就我们的目的而言,一个简单的MAJOR.MINOR.PATCH 格式就足够了。

根据semver ,当你的修改破坏了你的API的现有用途时,MAJOR 应该增加。MINOR 是为增加的功能保留的,同时保持向后兼容。当我们的修改旨在修复一个错误时,PATCH 应该增加。

在将你的修改提交到git 之后,运行npm version ,然后再运行major,minor, 或patch 将会相应的增加版本。

Git 标签

Git 标签用于将特定的版本与 Git 历史中的某一点联系起来,这在与使用非最新版本软件包的用户合作时非常有用。

要创建一个标签,你可以运行git tag v0.0.0 -m initial release 并加上相应的版本和注释。

一个更好的npm publish

由于增加了测试、提升版本和标记我们的发布,在发布新版本时很有可能不小心跳过一个步骤。为了帮助安全地发布我们的软件包,我们可以依靠 np.

$ npm i --save-dev np

在我们的package.json ,我们将添加一个release 脚本,运行buildnp

{
  "scripts": {
    "clean": "rimraf outline solid package *.tgz",
    "export": "figma-export use-config figma.config.js",
    "test": "node test.js",
    "build": "run-s clean export",
    "np": "np",
    "release": "run-s build np"
  }
}

为了发布我们的版本,我们应该首先提交我们的修改--不用推送,因为np 会为我们推送我们的修改--然后我们应该运行。

$ npm run release

这将构建我们的软件包,并提示我们提升我们的版本,并根据我们的Git仓库和npm依赖关系进行多次检查。np ,然后会在GitHub中打开一个新的发布页面,让我们对最新的标签进行注释并添加附件。

使用SVG作为React组件

用户有多种方法可以将SVG转换为React组件使用。然而,我们可以在前期做一些工作,使我们的包更加通用。

我们将依靠 [svgr](https://react-svgr.com/)来将我们的SVG转换成React组件。而像svgofigma-export ,也有一个我们要使用的svgr 的插件。

$ npm i --save-dev @figma-export/output-components-as-svgr

figma.config.js ,我们可以给我们的outputters 数组添加一个额外的值,就像这样。

const outputters = [
  require("@figma-export/output-components-as-svg")({ output: "./" }),
  require("@figma-export/output-components-as-svgr")({
    output: "./src",
  }),
];

运行npm run export 现在将创建一个src 目录,该目录下有outlinesolid 子目录,存放我们的React组件。

这些React组件是用JSX编写的,这意味着用户将需要一个build 步骤来消费它们。因此,我们将使用一个捆绑器将组件转译成JavaScript。我们还将添加TypeScript支持。

$ npm i --save-dev react rollup typescript @rollup/plugin-typescript @types/react

我们将首先在我们项目的根部为Rollup创建一个index.js 入口点,它将导出我们的React组件。

// ./index.js
export * from "./src/solid";
export * from "./src/outline";

然后,修改我们的outputters ,生成tsx 文件。

const capitalize = (s) => s.charAt(0).toUpperCase() + s.slice(1);

const fileId = process.env.FILE_ID;
const outputters = [
  require("@figma-export/output-components-as-svg")({ output: "./" }),
  require("@figma-export/output-components-as-svgr")({
    getFileExtension: () => ".tsx",
    getComponentName: ({ componentName, pageName }) =>
      componentName + capitalize(pageName),
    getSvgrConfig: () => ({ typescript: true }),
    output: "./src",
  }),
];

接下来,我们将创建tsconfig.jsonrollup.config.js 文件,以及一个bundle npm 脚本,将我们的文件捆绑到一个react/index.js 文件。

// tsconfig.json
{
  "compilerOptions": {
    "jsx": "react",
    "strict": true,
    "noImplicitAny": true,
    "allowSyntheticDefaultImports": true,
    "declaration": true,
    "outDir": "react"
  }
}



// rollup.config.js
import typescript from "@rollup/plugin-typescript";

const config = [
  {
    input: "index.js",
    output: {
      dir: "react",
      format: "module",
    },
    plugins: [typescript()],
  },
];

export default config;



{
  "scripts": {
    "bundle": "rollup --config",
    "build": "run-s clean export bundle",
  }
}

运行npm run build ,将生成一个react 目录,其中的index.js ,存放我们捆绑的组件,我们将在package.json 中指定这些组件作为我们的main 条目进入软件包。

{
  "main": "react/index.js"
}

用户将能够通过导入这些组件来使用它们。

import { HouseSolid, GlobeOutline } from 'iconlib'

为了发布React组件的支持,我们必须首先将react 目录添加到npm的filesclean 脚本中,并在运行src 之前将.gitignore 旁边的npm run release 。我们还应该添加一个 [peerDependencies](https://nodejs.org/es/blog/npm/peer-dependencies/)字段,以表明使用我们的包需要react

// package.json
"files": [
  "outline/",
  "solid/",
  "react/"
],
"peerDependencies": {
  "react": ">= 16"
}


// package.json
"scripts": {
  "clean": "rimraf outline solid react src package *.tgz"
}


# .gitignore

# build
/outline
/solid
/react
/src

使用精灵和CDN

Sprites是容纳我们所有图标的单个文件,它是使用SVG的另一种方式。为了显示一个图标,我们依靠use SVG元素。

<svg>
  <use xlink:href="path/to/sprite.svg#our-icon"/>
</svg>

对于某些项目来说,精灵是消费SVG的最合适的方式。因此,我们将为我们的SVG导出sprite,并通过包CDN使其随时可用。

首先,我们可以将@figma-export/output-components-as-svgstore ,作为一个开发依赖项来安装。

$ npm i --save-dev @figma-export/output-components-as-svgstore

然后我们可以在我们的outputters 数组中添加另一个项目,并指向一个sprite 目录。我们也将我们的图标名称作为ids,并将其小写。

const outputters = [
  // ...
  require("@figma-export/output-components-as-svgstore")({
    getIconId: ({ componentName }) => componentName.toLowerCase(),
    output: "./sprite",
  }),
];

运行npm run export 会在一个sprite 目录中生成精灵。

通过将我们的sprite 目录添加到.gitignoreclean 脚本和files 数组中,我们可以将我们的精灵发布到npm。

# .gitignore

# build
/outline
/solid
/react
/src
/sprite


// package.json
"files": [
  "outline/",
  "solid/",
  "react/",
  "sprite/"
]


// package.json
"scripts": {
  "clean": "rimraf outline solid react src sprite package *.tgz"
}

outline solid 然后,当有人通过CDN(如unpkgjsDelivr)访问我们的软件包时,我们可以通过在我们的unpkgcdnjsdelivr 字段中添加package.json ,将它们指向我们的精灵。

{  
  "jsdelivr": "sprite/outline.svg",
  "cdn": "sprite/outline.svg",
  "unpkg": "sprite/outline.svg"
}

如果有人访问 [https://unkg.com/$package_name](https://unkg.com/$package_name)的时候,这个URL就会解析到我们的outline.svg sprites。

总结

这篇博文涵盖了向npm发布,创建优化的图标,以及直接从Figma导出图标。我们还解决了测试问题,并添加了React和精灵支持。请在下面告诉我们您的实施情况

The postUsing npm to create JavaScript icon librariesappeared first onLogRocket Blog.