Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hbs strategy #186

Merged
merged 1 commit into from
Mar 24, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions addon/components/svg/index.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{{#if this._svgComponentName}}
{{#let (component this._svgComponentName) as |SvgJar|}}
<SvgJar
{{did-update this.updateSvg @name}}
@title={{@title}}
@titleId={{this.titleId}}
aria-label={{@ariaLabel}}
aria-hidden={{this.isAriaHidden}}
aria-labelledby={{this.titleId}}
...attributes
/>
{{/let}}
{{/if}}
78 changes: 78 additions & 0 deletions addon/components/svg/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import resolveAsset from 'ember-cli-resolve-asset';
import { action } from '@ember/object';
import { guidFor } from '@ember/object/internals';


function needsLoading(name) {
let componentDefinedName = `ember-svg-jar/components/${name}`;
try {
if (window.require(componentDefinedName)) {
return false;
}
} catch (e) {} // eslint-disable-line

return true;
}

function getInvocationName(name) {
return `ember-svg-jar@${name}`;
}

export async function loadSvg(name) {
let componentDefinedName = `ember-svg-jar/components/${name}`;
let invokationName = getInvocationName(name);

if (!needsLoading(name)) {
return invokationName;
}

const assetPath = await resolveAsset(`${componentDefinedName}.js`);
await import(assetPath);
return invokationName;
}

export default class Svg extends Component {
constructor() {
super(...arguments);
this.updateSvg();
}

@tracked _svgComponentName = null;

@action
async updateSvg() {
if (needsLoading(this.args.name)) {
if (this.args.loadingSvg) {
if (needsLoading(this.args.loadingSvg)) {
// maybe we didn't bundle the loadingSvg, it will be there for the next one.
loadSvg(this.args.loadingSvg);
} else {
this._svgComponentName = getInvocationName(this.args.loadingSvg);
}
}
let invokationName = await loadSvg(this.args.name);
if (!this.isDestroyed || !this.isDestroying) {
this._svgComponentName = invokationName;
this.isLoading = false;
if (this.args.onIconLoad && typeof this.args.onIconLoad === 'function') {
this.args.onIconLoad();
}
}
} else {
this._svgComponentName = getInvocationName(this.args.name);
}
}

get isAriaHidden() {
return (!this.args.ariaLabel && !this.args.ariaLabelledBy && !this.args.title);
}

get titleId() {
if (!this.args.ariaLabel && !this.args.ariaLabelledBy && this.args.title) {
return guidFor(`${this.args.name}-${this.title}`);
}
return '';
}
}
1 change: 1 addition & 0 deletions app/components/svg.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from 'ember-svg-jar/components/svg';
11 changes: 10 additions & 1 deletion ember-cli-build.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,18 @@ const EmberAddon = require('ember-cli/lib/broccoli/ember-addon');
module.exports = function(defaults) {
let app = new EmberAddon(defaults, {
svgJar: {
strategy: ['symbol', 'inline'],
strategy: ['symbol', 'inline', 'hbs'],
sourceDirs: ['tests/dummy/public'],
stripPath: false
},
// ember-cli-resolve-asset config
fingerprint: {
enabled: true,
generateAssetMap: true, // Required.
fingerprintAssetMap: true // Recommended to prevent caching issues.
},
'ember-fetch': {
preferNative: true // Recommended to enable faster preloading for browsers that support it.
}
});

Expand Down
7 changes: 6 additions & 1 deletion lib/build-options.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ function buildOptions(app) {
persist: true,

validations: {
throwOnFailure: false,
validateViewBox: true,
checkForDuplicates: true
},
Expand All @@ -81,6 +80,12 @@ function buildOptions(app) {
copypastaGen: defaultGens.inlineCopypastaGen
},

hbs: {
idGen: defaultGens.hbsIdGen,
copypastaGen: defaultGens.hbsCopypastaGen,
prefix: 'svg/',
},

symbol: {
idGen: defaultGens.symbolIdGen,
copypastaGen: defaultGens.symbolCopypastaGen,
Expand Down
8 changes: 7 additions & 1 deletion lib/default-gens.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
'use strict';

const {
generateComponentName
} = require('./utils');

module.exports = {
symbolIdGen: (svgPath, { prefix }) => `${prefix}${svgPath}`.replace(/[\s]/g, '-'),
symbolCopypastaGen: (assetId) => `{{svg-jar "#${assetId}"}}`,
inlineIdGen: (svgPath) => svgPath,
inlineCopypastaGen: (assetId) => `{{svg-jar "${assetId}"}}`
inlineCopypastaGen: (assetId) => `{{svg-jar "${assetId}"}}`,
hbsIdGen: (svgPath, { prefix }) => `${prefix}${generateComponentName(svgPath).dashCase}`,
hbsCopypastaGen: (assetId) => `<Svg @name="${generateComponentName(assetId).dashCase}" />`
};
78 changes: 78 additions & 0 deletions lib/hbs-packer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
'use strict';

const fs = require('fs');
const path = require('path-posix');
const CachingWriter = require('broccoli-caching-writer');
const {
readFile, svgDataFor, toPosixPath, saveToFile, relativePathFor
} = require('./utils');

function ensureDirectoryExistence(filePath) {
const dirname = path.dirname(filePath);
if (fs.existsSync(dirname)) {
return;
}
ensureDirectoryExistence(dirname);
fs.mkdirSync(dirname);
}


const templateOnlyComponent = (svg) => `
import templateOnly from '@ember/component/template-only';
import { setComponentTemplate } from '@ember/component';
import { hbs } from 'ember-cli-htmlbars';

export default setComponentTemplate(hbs\`${svg}\`, templateOnly());
`;

function formatAttrs(attrs) {
return Object.keys(attrs)
.map((key) => attrs[key] && `${key}="${attrs[key]}"`)
.filter((attr) => attr)
.join(' ');
}

const SVGAsHSBTemplate = ({ attrs, content }) => `
<svg ${formatAttrs(attrs)} ...attributes>
{{#if @title}}
<title id={{@titleId}}> {{@title}} </title>
{{/if}}
${content}
</svg>
`;

function createJS(_path, svgData) {
ensureDirectoryExistence(_path);
let svgHbsTemplate = SVGAsHSBTemplate(svgData);
let component = templateOnlyComponent(svgHbsTemplate);
saveToFile(_path, component);
}

class HbsPacker extends CachingWriter {
constructor(inputNode, opts = {}) {
super([inputNode], opts);
this.options = opts;
}

build() {
let inputPath = toPosixPath(this.inputPaths[0]);
let outputPath = path.join(toPosixPath(this.outputPath), 'components');
let { makeAssetID } = this.options;

this.listFiles()
.forEach((_filePath) => {
let filePath = toPosixPath(_filePath);
let relativePath = relativePathFor(filePath, inputPath);
let svgData = svgDataFor(readFile(filePath), {
xmlMode: false,
lowerCaseAttributeNames: false
});

let componentName = makeAssetID(relativePath);

createJS(path.join(outputPath, `${componentName}.js`), svgData);
});
}
}

module.exports = HbsPacker;
33 changes: 30 additions & 3 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const SVGOptimizer = require('broccoli-svg-optimizer');
const broccoliReplace = require('broccoli-string-replace');
const Symbolizer = require('./symbolizer/symbolizer');
const InlinePacker = require('./inline-packer');
const HbsPacker = require('./hbs-packer');
const ViewerAssetsBuilder = require('./viewer-assets-builder');
const ViewerBuilder = require('./viewer-builder');
const buildOptions = require('./build-options');
Expand All @@ -24,6 +25,10 @@ function mergeTreesIfNeeded(trees, options) {
module.exports = {
name: 'ember-svg-jar',

options: {
createdComponents: []
},

isDevelopingAddon() {
return false;
},
Expand Down Expand Up @@ -58,16 +63,23 @@ module.exports = {
trees.push(this.getSymbolStrategyTree());
}

if (this.hasHbsStrategy()) {
trees.push(...this.options.createdComponents);
}

return mergeTreesIfNeeded(trees);
},

treeForAddon(addonTree) {
let trees = [addonTree];

if (this.hasHbsStrategy()) {
this.options.createdComponents.push(
this._super.treeForAddon.call(this, this.getHbsStrategyTree())
);
}
if (this.hasInlineStrategy()) {
trees.push(this.getInlineStrategyTree());
}

return this._super.treeForAddon.call(this, mergeTreesIfNeeded(trees));
},

Expand Down Expand Up @@ -131,7 +143,6 @@ module.exports = {
let idGen = this.optionFor(strategy, 'idGen');
let stripPath = this.optionFor(strategy, 'stripPath');
let prefix = this.optionFor(strategy, 'prefix');

return new ViewerAssetsBuilder(this.svgsFor(strategy), {
strategy,
validationConfig: this.svgJarOptions.validations,
Expand All @@ -148,6 +159,18 @@ module.exports = {
});
},

getHbsStrategyTree() {
let idGen = this.optionFor('hbs', 'idGen');
let stripPath = this.optionFor('hbs', 'stripPath');
let prefix = this.optionFor('hbs', 'prefix');

return new HbsPacker(this.svgsFor('inline'), {
makeAssetID(relativePath) {
return makeIDForPath(relativePath, { idGen, stripPath, prefix });
}
});
},

getInlineStrategyTree() {
let idGen = this.optionFor('inline', 'idGen');
let stripPath = this.optionFor('inline', 'stripPath');
Expand Down Expand Up @@ -179,6 +202,10 @@ module.exports = {
return this.optionFor(strategy, 'optimizer');
},

hasHbsStrategy() {
return this.svgJarOptions.strategy.indexOf('hbs') !== -1;
},

hasInlineStrategy() {
return this.svgJarOptions.strategy.indexOf('inline') !== -1;
},
Expand Down
4 changes: 0 additions & 4 deletions lib/inline-packer.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,15 @@
Packages SVGs as ES modules for use with the inline strategy.
Required options:
makeAssetID

Examples of input and output:
Input node:
├── alarm.svg
└── cat.svg

Output node:
inlined
├── alarm.js
└── cat.js

alarm.js can content:

export default {
content: '<path>', attrs: { viewBox: '' }
}
Expand Down
33 changes: 29 additions & 4 deletions lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,8 @@ function makeIDForPath(relativePath, { idGen, stripPath, prefix }) {
)(relativePath);
}

function svgDataFor(svgContent) {
let $svg = cheerio.load(svgContent, { xmlMode: true })('svg');

function svgDataFor(svgContent, options = { xmlMode: true }) {
let $svg = cheerio.load(svgContent, options)('svg');
return {
content: $svg.html(),
attrs: $svg.attr()
Expand All @@ -46,11 +45,37 @@ function saveToFile(filePath, fileContents) {
fs.writeFileSync(filePath, fileContents);
}

function generateComponentName(
identifier
) {
let id = identifier.replace(/_\s*$/, '')
.replace(/\./g, '')
.trim();
const dashCase = id.replace(/^([A-Z])|[\s_](\w)/g, function(match, p1, p2) {
if (p2) return `-${p2}`;
return p1;
}).toLowerCase();

const pascalCase = dashCase
.replace(/(\w)(\w*)/g, function(_, g1, g2) {
return `${g1.toUpperCase()}${g2.toLowerCase()}`;
})
.replace(/\//g, '::')
.replace(/-/g, '');

return {
original: id,
dashCase,
pascalCase
};
}

module.exports = {
makeIDForPath,
relativePathFor,
svgDataFor,
readFile,
saveToFile,
toPosixPath
toPosixPath,
generateComponentName
};
2 changes: 1 addition & 1 deletion lib/validate-options.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
const _ = require('lodash');
const consoleUI = require('./console-ui');

const VALID_STRATEGIES = ['inline', 'symbol'];
const VALID_STRATEGIES = ['inline', 'symbol', 'hbs'];

function formatMessage(message) {
return `(options validation) ${message}`;
Expand Down
Loading