Compare commits

...

23 Commits

Author SHA1 Message Date
Eric Fennis
d349be0fee 0.1.0-alpha.0 2020-08-19 21:58:57 +02:00
Eric Fennis
c7a8ea09d9 Merge branch 'master' of github.com:featherity/featherity into feature/new-package 2020-08-19 21:36:37 +02:00
Eric Fennis
6fb9593f28 remove vscode setting files 2020-08-19 21:34:29 +02:00
Eric Fennis
2f16a80e6d reset packages directory 2020-08-19 20:45:49 +02:00
Eric Fennis
4bdee5a5a2 add indentation 2020-08-19 20:11:21 +02:00
Eric Fennis
41b9d7637d Bring back old libraries 2020-08-19 20:03:38 +02:00
Eric Fennis
d6b50f942a remove lint disabler 2020-08-19 20:01:34 +02:00
Eric Fennis
3a44f64098 fix lint error 2020-08-19 20:01:08 +02:00
Eric Fennis
6a0f52daab Refactor scripts 2020-08-19 19:57:21 +02:00
Eric Fennis
e38ad92be1 Setup rollup and build progress 2020-08-18 21:31:10 +02:00
Eric Fennis
70b178215b Move packeges 2020-08-14 23:20:19 +02:00
Eric Fennis
bdc4f81aa5 rename import 2020-07-18 13:18:00 +02:00
Eric Fennis
018a3af0bd Eslint fixes 2020-07-18 13:13:57 +02:00
Eric Fennis
c9551388d0 Add eslint config 2020-07-18 12:54:53 +02:00
Eric Fennis
4365dddf57 Improve optimize script 2020-07-18 12:53:05 +02:00
Eric Fennis
ea8bec9320 Factoring 2020-07-18 12:39:08 +02:00
Eric Fennis
e08eff7dee rename variable 2020-07-18 12:22:43 +02:00
Eric Fennis
4ac9e0bc37 remove log 2020-07-18 01:26:14 +02:00
Eric Fennis
446fd3f852 update package.json 2020-07-18 01:24:49 +02:00
Eric Fennis
e732f4dd6d Refactor names 2020-07-18 01:14:30 +02:00
Eric Fennis
771cf1d66e Add introduction readme 2020-07-18 01:01:56 +02:00
Eric Fennis
eee84b0edf Add build scripts for dist 2020-07-18 00:52:09 +02:00
Eric Fennis
de6906e242 New setup for new NPM package 2020-07-17 23:07:51 +02:00
21 changed files with 8050 additions and 28 deletions

11
.editorconfig Normal file
View File

@@ -0,0 +1,11 @@
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
quote_type = single
max_line_length = 100

4
.eslintignore Normal file
View File

@@ -0,0 +1,4 @@
dist
build
coverage
lib

21
.eslintrc.json Normal file
View File

@@ -0,0 +1,21 @@
{
"env": {
"browser": true,
"node": true
},
"extends": ["airbnb-base", "prettier"],
"plugins": ["import", "prettier"],
"rules": {
"no-console": "off",
"no-param-reassign": "off",
"no-shadow": "off",
"no-use-before-define": "off",
"prettier/prettier": [
"error",
{
"singleQuote": true,
"trailingComma": "all"
}
]
}
}

6
.gitignore vendored
View File

@@ -1,6 +1,10 @@
.DS_Store
node_modules
dist
build
lib
sandbox
stash
coverage
coverage
stats
*.log

View File

@@ -13,12 +13,64 @@ Featherity is a fork of [Feather Icons](https://github.com/feathericons/feather)
* [Contributing](#contributing)
* [License](#license)
## Installation
``` bash
npm install featherity
#or
yarn add featherity
```
## Usage
At its core, Featherity is a collection of [SVG](https://svgontheweb.com/#svg) files. This means that you can use Feather icons in all the same ways you can use SVGs (e.g. `img`, `background-image`, `inline`, `object`, `embed`, `iframe`). Here's a helpful article detailing the many ways SVGs can be used on the web: [SVG on the Web Implementation Options](https://svgontheweb.com/#implementation)
The following are additional ways you can use Featherity.
### ESModule
``` js
import { Camera } from 'featherity';
// Returns HTMLElement
// Usage
document.appendChild(Camera);
```
### React
``` js
import { Camera } from 'featherity/react';
// Returns ReactComponent
// Usage
const App = () => {
return <Camera color="red" size={48}/>
};
export default App;
```
### Vue
``` vue
<template>
<div id="app">
<Camera color="red" :size="48"/>
</div>
</template>
<script>
import { Camera } from 'featherity/vue';
export default {
name: "App",
components: {
Camera
}
};
</script>
```
### Figma
You can use the components from [this Figma file](https://www.figma.com/file/g0UipfQlRfGrntKPxZknM7/Featherity).

28
babel.config.js Normal file
View File

@@ -0,0 +1,28 @@
module.exports = {
presets: [
[
'@babel/env',
{
loose: true,
modules: false,
},
],
],
// plugins: ['babel-plugin-add-import-extension'],
env: {
test: {
presets: ['@babel/env'],
plugins: ['@babel/plugin-transform-runtime'],
},
dev: {
plugins: [
[
'transform-inline-environment-variables',
{
include: ['NODE_ENV'],
},
],
],
},
},
};

View File

@@ -1,9 +1,61 @@
{
"name": "featherity",
"amdName": "featherity",
"license": "ISC",
"version": "0.1.0-alpha.0",
"private": true,
"source": "build/featherity.js",
"main": "dist/cjs/featherity.js",
"main:umd": "dist/umd/featherity.js",
"module": "lib/featherity.js",
"unpkg": "dist/umd/featherity.min.js",
"sideEffects": false,
"scripts": {
"react:compile": "yarn workspace react compile"
"start": "babel-watch --watch src",
"clean": "rimraf lib && rimraf dist && rimraf build",
"build": "yarn clean && yarn build:transpile && yarn build:icons && yarn build:es && yarn build:esbrowser && yarn build:bundles",
"build:transpile": "babel src -d build",
"build:icons": "npx babel-node ./scripts/buildIcons.js --presets @babel/env",
"build:es": "babel build -d lib --source-maps --ignore '**/*.test.js','**/__mocks__'",
"build:esbrowser": "BROWSER_COMPAT=true yarn build:es -d dist/esm",
"build:bundles": "rollup -c rollup.config.js",
"optimize": "npx babel-node ./scripts/optimizeSvgs.js --presets @babel/env"
},
"workspaces": [
"packages/react"
]
"devDependencies": {
"@ampproject/rollup-plugin-closure-compiler": "^0.25.2",
"@atomico/rollup-plugin-sizes": "^1.1.4",
"@babel/cli": "^7.10.5",
"@babel/core": "^7.11.1",
"@babel/node": "^7.10.5",
"@babel/preset-env": "^7.11.0",
"@rollup/plugin-babel": "^5.0.0",
"@rollup/plugin-replace": "^2.3.2",
"babel-plugin-add-import-extension": "^1.4.3",
"cheerio": "^1.0.0-rc.2",
"eslint": "^4.19.1",
"eslint-config-airbnb-base": "^12.1.0",
"eslint-config-prettier": "^2.9.0",
"eslint-plugin-import": "^2.5.0",
"eslint-plugin-prettier": "^2.5.0",
"html-minifier": "^3.5.8",
"lint-staged": "^6.0.0",
"microbundle": "^0.12.3",
"prettier": "^1.8.2",
"rollup": "^2.7.3",
"rollup-plugin-commonjs": "^10.1.0",
"rollup-plugin-flow-entry": "^0.3.3",
"rollup-plugin-license": "^2.0.0",
"rollup-plugin-node-resolve": "^5.2.0",
"rollup-plugin-terser": "^5.2.0",
"rollup-plugin-visualizer": "^4.1.0",
"semantic-release": "^12.2.2",
"svgo": "^1.3.2"
},
"dependencies": {
"core-js": "3",
"htmlparser2": "^4.1.0",
"lodash": "^4.17.19",
"prop-types": "^15.7.2",
"react": "^16.13.1"
}
}

61
rollup.config.js Normal file
View File

@@ -0,0 +1,61 @@
import babel from '@rollup/plugin-babel';
import replace from '@rollup/plugin-replace';
import bundleSize from '@atomico/rollup-plugin-sizes';
import compiler from '@ampproject/rollup-plugin-closure-compiler';
import { terser } from 'rollup-plugin-terser';
import visualizer from 'rollup-plugin-visualizer';
import license from 'rollup-plugin-license';
import resolve from 'rollup-plugin-node-resolve';
import commonJS from 'rollup-plugin-commonjs';
import pkg from './package.json';
const outputFileName = pkg.name;
const inputs = ['build/featherity.js'];
const bundles = [
{ inputs, format: 'umd', dir: 'dist', minify: true },
{ inputs, format: 'umd', dir: 'dist' },
{ inputs, format: 'cjs', dir: 'dist' },
];
const configs = bundles
.map(({ inputs, dir, format, minify }) =>
inputs.map(input => ({
input,
external: ['lodash/camelCase', 'lodash/upperFirst'],
plugins: [
format === 'umd' &&
replace({
__DEV__: minify ? 'false' : 'true',
}),
babel({ babelHelpers: 'bundled' }),
// The two minifiers together seem to procude a smaller bundle 🤷‍♂️
minify && compiler(),
minify && terser(),
license({ banner: `${pkg.name} v${pkg.version} - ${pkg.license}` }),
bundleSize(),
resolve(),
commonJS({
include: 'node_modules/**',
}),
visualizer({
sourcemap: true,
filename: `stats/${outputFileName}${minify ? '-min' : ''}.html`,
}),
].filter(Boolean),
output: {
name: 'featherity',
file: `${dir}/${format}/${outputFileName}${minify ? '.min' : ''}.js`,
format,
sourcemap: true,
exports: 'named',
globals: {
'lodash/camelCase': 'camelCase',
'lodash/upperFirst': 'upperFirst',
},
},
})),
)
.flat();
export default configs;

View File

@@ -0,0 +1,23 @@
import path from 'path';
import { generateComponentName, resetFile, writeFile, readFile } from './helpers';
export default function(inputEntry, outputDirectory, componentGetter, iconNodes) {
const fileName = path.basename(inputEntry);
// Reset file
resetFile(fileName, outputDirectory);
const icons = Object.keys(iconNodes);
// Generate Import for Icon VNodes
icons.forEach(iconName => {
const componentName = generateComponentName(iconName);
const importString = `export { default as ${componentName} } from './${iconName}';\n`;
writeFile(importString, fileName, outputDirectory);
});
writeFile('\n', fileName, outputDirectory);
console.log(`Successfully generated ${fileName} file`);
}

View File

@@ -0,0 +1,27 @@
/* eslint-disable import/no-extraneous-dependencies */
import fs from 'fs';
import path from 'path';
import prettier from 'prettier';
import { generateComponentName } from './helpers';
export default function(iconNode, outputDirectory, template) {
const icons = Object.keys(iconNode);
const iconsDistDirectory = path.join(outputDirectory, `icons`);
if (!fs.existsSync(iconsDistDirectory)) {
fs.mkdirSync(iconsDistDirectory);
}
icons.forEach(icon => {
const location = path.join(iconsDistDirectory, `${icon}.js`);
const componentName = generateComponentName(icon);
const node = JSON.stringify(iconNode[icon]);
const elementTemplate = template({ componentName, node });
fs.writeFileSync(location, prettier.format(elementTemplate, { parser: 'babel' }), 'utf-8');
console.log('Successfully built', componentName);
});
}

37
scripts/build/helpers.js Normal file
View File

@@ -0,0 +1,37 @@
import { upperFirst, camelCase } from 'lodash/string';
import fs from 'fs';
import path from 'path';
/**
* Generates a componentName of a String.
*
* @param {string} iconName
*/
export const generateComponentName = iconName =>
iconName === 'github' ? 'GitHub' : upperFirst(camelCase(iconName));
/**
* Resets the file contents.
*
* @param {string} fileName
* @param {string} outputDirectory
*/
export const resetFile = (fileName, outputDirectory) =>
fs.writeFileSync(path.join(outputDirectory, fileName), '', 'utf-8');
/**
* Reads the file contents.
*
* @param {string} path
*/
export const readFile = entry => fs.readFileSync(path.resolve(__dirname, '../../', entry), 'utf-8');
/**
* writes content to a file
*
* @param {string} content
* @param {string} fileName
* @param {string} outputDirectory
*/
export const writeFile = (content, fileName, outputDirectory) =>
fs.appendFileSync(path.join(outputDirectory, fileName), content, 'utf-8');

43
scripts/buildIcons.js Normal file
View File

@@ -0,0 +1,43 @@
import fs from 'fs';
import path from 'path';
import renderIconsObject from './render/renderIconsObject';
import renderIconNodes from './render/renderIconNodes';
import generateIconFiles from './build/generateIconFiles';
import generateExportsFile from './build/generateExportsFile';
const IN_DIR = path.resolve(__dirname, '../icons');
const OUTPUT_DIR = path.resolve(__dirname, '../build');
const SRC_DIR = path.resolve(__dirname, '../src');
if (!fs.existsSync(OUTPUT_DIR)) {
fs.mkdirSync(OUTPUT_DIR);
}
const svgFiles = fs.readdirSync(IN_DIR).filter(file => path.extname(file) === '.svg');
const getSvg = svgFile => fs.readFileSync(path.join(IN_DIR, svgFile));
const icons = renderIconsObject(svgFiles, getSvg);
const iconVNodes = renderIconNodes(icons);
// Generates iconsNodes files for each icon
generateIconFiles(
iconVNodes,
OUTPUT_DIR,
({ componentName, node }) => `
import createElement from '../../src/createElement';
const ${componentName} = ${node};
export const element = createElement('${componentName}', ${componentName});
export default ${componentName};
`,
);
// Generates entry files for the compiler filled with icons exports
generateExportsFile(
path.join(SRC_DIR, 'icons/index.js'),
path.join(OUTPUT_DIR, 'icons'),
'getElement',
iconVNodes,
);

17
scripts/optimizeSvgs.js Normal file
View File

@@ -0,0 +1,17 @@
import fs from 'fs';
import path from 'path';
import processSvg from './render/processSvg';
const ICONS_DIR = path.resolve(__dirname, '../icons');
console.log(`Optimizing SVGs in ${ICONS_DIR}...`);
const svgFiles = fs.readdirSync(ICONS_DIR).filter(file => path.extname(file) === '.svg');
console.log(svgFiles);
svgFiles
.filter(file => path.extname(file) === '.svg')
.forEach(svgFile => {
const svg = fs.readFileSync(path.join(ICONS_DIR, svgFile));
processSvg(svg).then(svg => fs.writeFileSync(path.join(ICONS_DIR, svgFile), svg));
});

View File

@@ -0,0 +1,11 @@
{
"xmlns": "http://www.w3.org/2000/svg",
"width": 24,
"height": 24,
"viewBox": "0 0 24 24",
"fill": "none",
"stroke": "currentColor",
"stroke-width": 2,
"stroke-linecap": "round",
"stroke-linejoin": "round"
}

View File

@@ -0,0 +1,57 @@
/* eslint-disable import/no-extraneous-dependencies */
import Svgo from 'svgo';
import cheerio from 'cheerio';
import { format } from 'prettier';
import DEFAULT_ATTRS from './default-attrs.json';
/**
* Process SVG string.
* @param {string} svg - An SVG string.
* @param {Promise<string>}
*/
function processSvg(svg) {
return (
optimize(svg)
.then(setAttrs)
.then(format)
// remove semicolon inserted by prettier
// because prettier thinks it's formatting JSX not HTML
.then(svg => svg.replace(/;/g, ''))
);
}
/**
* Optimize SVG with `svgo`.
* @param {string} svg - An SVG string.
* @returns {Promise<string>}
*/
function optimize(svg) {
const svgo = new Svgo({
plugins: [
{ convertShapeToPath: false },
{ mergePaths: false },
{ removeAttrs: { attrs: '(fill|stroke.*)' } },
{ removeTitle: true },
],
});
return new Promise(resolve => {
svgo.optimize(svg, ({ data }) => resolve(data));
});
}
/**
* Set default attibutes on SVG.
* @param {string} svg - An SVG string.
* @returns {string}
*/
function setAttrs(svg) {
const $ = cheerio.load(svg);
Object.keys(DEFAULT_ATTRS).forEach(key => $('svg').attr(key, DEFAULT_ATTRS[key]));
return $('body').html();
}
export default processSvg;

View File

@@ -0,0 +1,29 @@
/* eslint-disable import/no-extraneous-dependencies */
import { parseDOM } from 'htmlparser2';
import DEFAULT_ATTRS from './default-attrs.json';
export default iconsObject => {
const iconNodes = {};
Object.keys(iconsObject).forEach(icon => {
const svgString = iconsObject[icon];
const dom = parseDOM(svgString);
const children = dom.map(element => [
element.name,
{
...element.attribs,
},
]);
iconNodes[icon] = [
'svg',
{
...DEFAULT_ATTRS,
},
children,
];
});
return iconNodes;
};

View File

@@ -0,0 +1,34 @@
/* eslint-disable import/no-extraneous-dependencies */
import path from 'path';
import cheerio from 'cheerio';
import { minify } from 'html-minifier';
/**
* Get contents between opening and closing `<svg>` tags.
* @param {string} svg
* @returns {string}
*/
function getSvgContents(svg) {
const $ = cheerio.load(svg);
return minify($('svg').html(), { collapseWhitespace: true });
}
/**
* Build an object in the format: `{ <name>: <contents> }`.
* @param {string[]} svgFiles - A list of filenames.
* @param {Function} getSvg - A function that returns the contents of an SVG file given a filename.
* @returns {Object}
*/
export default (svgFiles, getSvg) =>
svgFiles
.map(svgFile => {
const name = path.basename(svgFile, '.svg');
const svg = getSvg(svgFile);
const contents = getSvgContents(svg);
return { name, contents };
})
.reduce((icons, icon) => {
icons[icon.name] = icon.contents;
return icons;
}, {});

19
src/createElement.js Normal file
View File

@@ -0,0 +1,19 @@
const createElement = (tag, attrs, children = []) => {
const element = document.createElement(tag);
Object.keys(attrs).forEach(name => {
element.setAttribute(name, attrs[name]);
});
if (children.length) {
children = children.forEach(child => {
const childElement = createElement(...child);
element.appendChild(childElement);
});
}
return element;
};
export default ([tag, attrs, children]) => createElement(tag, attrs, children);

5
src/icons/index.js Normal file
View File

@@ -0,0 +1,5 @@
/*
Icons exports.
Will be generated
*/

40
src/replaceElement.js Normal file
View File

@@ -0,0 +1,40 @@
import camelCase from 'lodash/camelCase';
import upperFirst from 'lodash/upperFirst';
import createElement from './createElement';
/**
* Get the attributes of an HTML element.
* @param {HTMLElement} element
* @returns {Object}
*/
function getAttrs(element) {
return Array.from(element.attributes).reduce((attrs, attr) => {
attrs[attr.name] = attr.value;
return attrs;
}, {});
}
export default (element, { nameAttr, icons, attrs }) => {
const iconName = element.getAttribute(nameAttr);
const ComponentName = upperFirst(camelCase(iconName));
const iconNode = icons[ComponentName];
if (!iconNode) {
return console.warn(
`${element.outerHTML} icon name was not found in the provided icons object.`,
);
}
const [, iconAttrs] = iconNode;
iconNode[1] = {
...iconAttrs,
...getAttrs(element),
...attrs,
};
const svgElement = createElement(iconNode);
return element.parentNode.replaceChild(svgElement, element);
};

7493
yarn.lock

File diff suppressed because it is too large Load Diff