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

feat(icons-vue): add vue 3 support #7545

Merged

Conversation

lee-chase
Copy link
Member

Closes #7310

Updates Vue icon-build-helper to work with Vue 2 and Vue 3.

Changelog

Changed

packages/icon-build-helpers/src/builders/vue.js

Testing / Reviewing

Verified working in a Vue 3 and Vue 2 project, with click handler, static and dynamic classes, static and dynamic styles.

@lee-chase lee-chase requested a review from a team as a code owner January 12, 2021 22:27
@lee-chase lee-chase requested review from emyarod and dakahn January 12, 2021 22:27
@netlify
Copy link

netlify bot commented Jan 12, 2021

✔️ Deploy preview for carbon-elements ready!

🔨 Explore the source changes: 6e7957b

🔍 Inspect the deploy logs: https://app.netlify.com/sites/carbon-elements/deploys/5ffe22674b4c7b00081b6399

😎 Browse the preview: https://deploy-preview-7545--carbon-elements.netlify.app

@netlify
Copy link

netlify bot commented Jan 12, 2021

✔️ Deploy preview for carbon-components-react ready!
Built without sensitive environment variables

🔨 Explore the source changes: 6e7957b

🔍 Inspect the deploy logs: https://app.netlify.com/sites/carbon-components-react/deploys/5ffe22673d5cca00070cd4bf

😎 Browse the preview: https://deploy-preview-7545--carbon-components-react.netlify.app

@netlify
Copy link

netlify bot commented Jan 12, 2021

Deploy preview for carbon-elements ready!

Built with commit 2885c3c

https://deploy-preview-7545--carbon-elements.netlify.app

@joshblack
Copy link
Contributor

Looking great @lee-chase! Let me push up a quick fix for the rollup bundle and then you should be good to go 😄

@netlify
Copy link

netlify bot commented Jan 12, 2021

✔️ Deploy preview for carbon-components-react ready!
Built without sensitive environment variables

🔨 Explore the source changes: 5508688

🔍 Inspect the deploy logs: https://app.netlify.com/sites/carbon-components-react/deploys/5ffe2272d22ca5000700c7bb

😎 Browse the preview: https://deploy-preview-7545--carbon-components-react.netlify.app

@netlify
Copy link

netlify bot commented Jan 12, 2021

Deploy preview for carbon-components-react ready!

Built without sensitive environment variables with commit 2885c3c

https://deploy-preview-7545--carbon-components-react.netlify.app

@joshblack
Copy link
Contributor

@lee-chase trying to diagnose the ENOMEM that we're seeing now, not sure why this would cause out-of-memory exceptions but will see what we can do 🤔

@lee-chase
Copy link
Member Author

Not sure @josh.

I was considering a slight refactor to the following, but will hold off while you investigate.

/**
 * Generate an icon component, which in our case is the string representation
 * of the component, from a given moduleName and icon descriptor.
 * @param {string} moduleName
 * @param {object} descriptor
 * @returns {object}
 */
function createIconComponent(moduleName, descriptor) {
  const { attrs, content } = descriptor;
  const attrsAsString = Object.keys(attrs)
    .map((attr) => `${attr}: "${attrs[attr]}"`)
    .join(',');

  const source = `${BANNER}
import { h } from 'vue';
import { getAttributes } from '@carbon/icon-helpers';

const common = {
  name: '${moduleName}',
  // We use title as the prop name as it is not a valid attribute for an SVG
  // HTML element
  props: ['title'],
};

const getSvgAttrs = (title, componentAttrs) => {
  return getAttributes({
    ${attrsAsString},
    preserveAspectRatio: 'xMidYMid meet',
    xmlns: 'http://www.w3.org/2000/svg',
    // Special case here, we need to coordinate that we are using title,
    // potentially, to get the right focus attributes
    title,
    ...componentAttrs
  });
};

const component = h ? {
  // Vue 3 component
  ...common,
  setup(props, { attrs }) {
    return () => h(
      'svg',
      getSvgAttrs(props.title, attrs),
      [
        props.title && h('title', props.title),
        ${content.map(convertToVue(3)).join(', ')},
      ]
    );
  },
} : {
  // Vue 2 component
  ...common,
  functional: true,
  render(createElement, context) {
    const { children, data, listeners, props } = context;
    const attrs = getSvgAttrs(props.title, data.attrs);
    const svgData = {
      attrs,
      on: listeners,
    };
    if (data.staticClass) {
      svgData.class = {
        [data.staticClass]: true,
      };
    }
    if (data.class) {
      svgData.class = svgData.class || {}; // may be no static class
      svgData.class[data.class] = true;
    }
    // remove style set by getAttributes
    delete svgData.attrs.style;
    // combine incoming staticStyle, style with default willChange
    svgData.style = { ...data.staticStyle, ...data.style };
    return createElement('svg', svgData, [
      props.title && createElement('title', null, props.title),
      ${content.map(convertToVue(2)).join(', ')},
      children,
    ]);
  },
};

export default component;
`;

  return source;
}

/**
 * Returns a function that will create a version appropriate Vue source string from a node
 * @param {number} version
 * @returns {Function}
 */
function convertToVue(version) {
  return (node) => {
    const { elem, attrs } = node;
    return version < 3
      ? `createElement('${elem}', { attrs: ${JSON.stringify(attrs)} })`
      : `h('${elem}', ${JSON.stringify(attrs)})`;
  };
}

@joshblack
Copy link
Contributor

@lee-chase spent way too long trying to make this work, just seems like no matter what the size of each file * number of files ends up with too many objects being allocated on the heap 😞

I think to move forward, we should see how we could mirror the changes to icons-react that included:

  • Consolidate "base icon functionality" behind a component (helps to reduce footprint)
    • With this approach, each "icon module" basically becomes a wrapper that uses the base icon component plus the svg data as children
  • Move entrypoint to our "bucket" strategy

@lee-chase
Copy link
Member Author

lee-chase commented Jan 15, 2021

  • behind a component

You are still generating @carbon/icons-react is the intent to drop that?
Given that @carbon/icons-vue is more popular that @carbon/vue I think there is a need to maintain it.

@joshblack
Copy link
Contributor

joshblack commented Jan 15, 2021

@lee-chase my comment might have been unclear, there is no intent to drop any packages or change generation logic. The hope was to share the strategy that is being used in @carbon/icons-react. Basically, each icon component generated wraps a base icon component:

// Icon base component
function Icon({ children, ...rest }) {
  return /* ... */
}

// Specific icon module / component
function Add16(props) {
  return <Icon {...props}><path d="..." /></Icon>;
}

This was brought up as one way to potentially reduce the file footprint of each icon module (since a good chunk of duplicate logic can live in the base component). This is needed since the build is experiencing ENOMEM errors from rollup trying to compile all of the modules at once for icons-vue

Copy link
Member

@emyarod emyarod left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it looks like there are some merge conflicts with the lockfile and package.json

@dcwarwick
Copy link
Contributor

Interestingly, this builds fine on Node 14, but fails on Node 12 whatever I do to the heap. So I think there is an issue in the Node 12 stream writing that we are hitting here. Nevertheless, I think it's worth refactoring the files along the lines @joshblack mentioned to reduce their overhead, because that brings several benefits and will make things neater and better performance when lots of icons are being used.

A base class probably isn't quite so good a fit in Vue 3 as it is in React, but let's push the function into some utility functions and bundle them with the icons -- specialisation by composition rather than inheritance if I remember my CompSci correctly :-). @lee-chase and I have done a first pass, and I'll just polish that and check it works and update the PR including merging master package changes too.

@joshblack
Copy link
Contributor

More info in case it's helpful, all the builds should be happening on Node v14 LTS IIRC, specifically for CircleCI that should be defined here.

And yeah, definitely didn't mean sub-classes/inheritance but using a base component (in the code snippet that was Icon) and then each specific icon module calls that and passes in the icon data as children 👍

@dcwarwick
Copy link
Contributor

@lee-chase I've got a refactor ready to commit, but I think you need to give me permission to update your branch.

@dcwarwick
Copy link
Contributor

@lee-chase @joshblack Here's a refactoring that greatly reduces the weight of each individual icon component file, delegating the work into common functions. It builds a lot faster too! I haven't merged the changes to package.jsons from master in here, because that will bring in a gazillion little yarn cache updates that will clutter things up, but I can easily do that once we're happy with this PR in order to make it mergeable.

@lee-chase
Copy link
Member Author

@emyarod @joshblack resolving conflicts in the yar.lock file gets dull very quickly every time someone else makes a change as I do not have permission to click resolve conflicts in this repo.

@joshblack
Copy link
Contributor

joshblack commented Jan 21, 2021

@dcwarwick that's awesome! Definitely will check it out this morning, super exciting!!

@lee-chase totally understand, I'm not sure if it's a permissions thing or more-so that through the UI GitHub can't arrange it automatically. If it's a permissions thing I can totally look into it, just let me know what access role it falls under and I can see if I can address it 👍

I went ahead and merged the upstream branch and fixed the merge conflicts, I also reverted the changes to CircleCI so we can check out the full build. Let me know if I missed anything!

@lee-chase
Copy link
Member Author

@dcwarwick that's awesome! Definitely will check it out this morning, super exciting!!

@lee-chase totally understand, I'm not sure if it's a permissions thing or more-so that through the UI GitHub can't arrange it automatically. If it's a permissions thing I can totally look into it, just let me know what access role it falls under and I can see if I can address it 👍

I went ahead and merged the upstream branch and fixed the merge conflicts, I also reverted the changes to CircleCI so we can check out the full build. Let me know if I missed anything!

Looks good to me @joshblack

@joshblack
Copy link
Contributor

bump @emyarod @dakahn should be good to go!

Copy link
Member

@emyarod emyarod left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good to me

@andreancardona andreancardona enabled auto-merge (squash) January 22, 2021 19:04
@joshblack joshblack changed the title fix: carbon icons vue for vue 3 feat(icons-vue): add vue 3 support Jan 22, 2021
@joshblack joshblack disabled auto-merge January 22, 2021 21:03
@joshblack joshblack enabled auto-merge (squash) January 22, 2021 21:03
@joshblack joshblack merged commit aebf79e into carbon-design-system:master Jan 22, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Bug: @carbon/icons-vue doesn't work with Vue 3
7 participants