Skip to content
This repository has been archived by the owner on Feb 22, 2023. It is now read-only.

Create a proof-of-concept for Pinia migration #906

Merged
merged 31 commits into from
Mar 3, 2022
Merged

Create a proof-of-concept for Pinia migration #906

merged 31 commits into from
Mar 3, 2022

Conversation

dhruvkb
Copy link
Member

@dhruvkb dhruvkb commented Feb 18, 2022

Migration to Pinia

Feel free to respond to this PR/RFC/proposal with

  • additional migration PRs (or commits)
  • comments suggesting changes to the approach

Goals

  • To completely eliminate all uses of Vuex, gradually replacing them with Pinia
  • To do the upgrade in a gradual way, entering a transition period where Vuex and Pinia coexist

Side-goals

  • Removal of the verbosity introduced by module and action constants
  • To create helpers, utils and patterns for testing store usage consistently in unit tests
  • Abandon the usage of the controversial useStore helper in favour of modular useX from Pinia stores

Steps

  1. Install Pinia and test compatibility (this PR)

  2. Replace Vuex stores with their Pinia counterparts

  • state -> state

  • getters -> getters

  • mutations and actions -> actions

    In this stage we must determine if we must use mutations as actions. Pinia allows changing the state directly in the components (which was frowned upon by Vuex) so we could do away with mutations in theory. But having them and following the Vuex principle makes transition smoother and keeps state changes limited to one file instead of scattered around the codebase.

  1. Update tests, tests involving changes to the Vuex store now need to be updated to work with Pinia.
  • beforeEach

    setActivePinia(createPinia())
  • in the test case

    const xyzStore = useXyz()
    expect(xyzStore.state).to // ...
  1. Remove unused constants from
  • ~/constants/mutation-types.js
  • ~/constants/action-types.js
  • ~/constants/store-modules.js

-1. In the end, with no Vuex stores remaining, Vuex and all associated dependencies will be removed. The store/index.js file can also be removed after the complete migration to Pinia. The disableVuex: false flag should also be removed.

Conventions

Nomenclature

  • In the transition, we name stores after their Vuex counterparts.
    • active.jsactiveStore / useActive
  • State is unchanged, except that it is now an arrow function that returns the dictionary.
  • Mutations become the camelCasedVersions of their constant names
    • SET_ACTIVE_MEDIA_ITEMsetActiveMediaItem

Type hinting

In the interest of a quick migration, we can continue to use the existing type definitions for the state and the various mutations/actions from the types.d.ts file. Some definitions in the file may be out of date (as was the case for ActiveMediaState), so a cursory glance to check that would be useful.

Procedure

To minimise disruption, each store will be changed using a lock-based approach where the developer takes a lock on the store using a notification to the group, makes a PR updating a single store and its usages and this lock is released after the PR is merged.

This prevents PRs from diverging too much and allows a seamless transition for all involved.

Fixes

Addresses #756 by @sarayourfriend
Addresses #831 by @obulat

PR description

This PR

  • sets up Pinia alongside Vuex
  • updates the smallest store, active, to Pinia
  • updates associated tests

Checklist

  • My pull request has a descriptive title (not a vague title like Update index.md).
  • My pull request targets the default branch of the repository (main) or a parent feature branch.
  • My commit messages follow best practices.
  • My code follows the established code style of the repository.
  • I added or updated tests for the changes I made (if applicable).
  • I added or updated documentation (if applicable).
  • I tried running the project locally and verified that there are no visible errors.

Developer Certificate of Origin

Developer Certificate of Origin
Developer Certificate of Origin
Version 1.1

Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
1 Letterman Drive
Suite D4700
San Francisco, CA, 94129

Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.


Developer's Certificate of Origin 1.1

By making a contribution to this project, I certify that:

(a) The contribution was created in whole or in part by me and I
    have the right to submit it under the open source license
    indicated in the file; or

(b) The contribution is based upon previous work that, to the best
    of my knowledge, is covered under an appropriate open source
    license and I have the right under that license to submit that
    work with modifications, whether created in whole or in part
    by me, under the same open source license (unless I am
    permitted to submit under a different license), as indicated
    in the file; or

(c) The contribution was provided directly to me by some other
    person who certified (a), (b) or (c) and I have not modified
    it.

(d) I understand and agree that this project and the contribution
    are public and that a record of the contribution (including all
    personal information I submit with it, including my sign-off) is
    maintained indefinitely and may be redistributed consistent with
    this project or the open source license(s) involved.

@dhruvkb dhruvkb added 🟨 priority: medium Not blocking but should be addressed soon 🌟 goal: addition Addition of new feature 💻 aspect: code Concerns the software code in the repository labels Feb 18, 2022
@dhruvkb dhruvkb changed the title Pinia Poc Create a proof-of-concept for Pinia migration Feb 18, 2022
@sarayourfriend
Copy link
Contributor

Please note that the ATTRIBUTION store is being removed in #905

@dhruvkb
Copy link
Member Author

dhruvkb commented Feb 18, 2022

Oh yeah, this was just a test to see if things work and if this is the right direction to proceed in, before we start migrating some of the more massive modules.

@zackkrida
Copy link
Member

@dhruvkb this looks excellent, great initial proof of concept. I'd love to see this followed up with a Pina RFC next week to outline further next steps in more detail.

src/store/attribution.js Outdated Show resolved Hide resolved
@sarayourfriend
Copy link
Contributor

-1. In the end, with no Vuex stores remaining, Vuex and all associated dependencies will be removed. The store/index.js file can also be removed after the complete migration to Pinia.

We should also make a note to remove the disableVuex: false flag from the pinia configuration.

Copy link
Contributor

@sarayourfriend sarayourfriend left a comment

Choose a reason for hiding this comment

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

I'd love to see adding a requirement be TypeScript types for each re-written store. To include this we'd also have to add each re-written store to the tsconfig.json includes list.

This is great though!

In this stage we must determine if we must use mutations as actions. Pinia allows changing the state directly in the components (which was frowned upon by Vuex) so we could do away with mutations in theory. But having them and following the Vuex principle makes transition smoother and keeps state changes limited to one file instead of scattered around the codebase.

I'd be nice to stick mutations in the Pinia store, if only because it'll make them significantly easier to test without having to rely on triggering them via some component interaction.

in the test case

We can really just call the useX store composable inside the test function like that? Vue won't complain about using composables outside of a component context?

In your time researching, did you come across any opinions about whether to use the object or function callback syntax for defining a store? I strongly prefer the function callback syntax because I feel like it more closely mirrors the way state is created in setup (in fact, it's exactly the same) and it also makes having private helper functions possible (compared to the object syntax where all the functions on the definition object will be public). It also eliminates this usage which I've generally found to be a good thing for comprehensibility in JavaScript generally.

src/layouts/default.vue Show resolved Hide resolved
src/store/active.js Outdated Show resolved Hide resolved
src/store/active.js Outdated Show resolved Hide resolved
src/store/active.js Outdated Show resolved Hide resolved
src/components/VAudioTrack/VAudioTrack.vue Outdated Show resolved Hide resolved
@dhruvkb dhruvkb marked this pull request as ready for review February 22, 2022 04:24
@dhruvkb dhruvkb requested a review from a team as a code owner February 22, 2022 04:24
# Conflicts:
#	src/locales/po-files/openverse.pot
@zackkrida zackkrida mentioned this pull request Mar 2, 2022
7 tasks
Copy link
Member

@zackkrida zackkrida left a comment

Choose a reason for hiding this comment

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

lgtm. My only remaining question is if we want to use the useNav naming convention or be extra redundant and clear with useNavStore, but that's a very minor concern we could change later.

@zackkrida zackkrida requested a review from obulat March 2, 2022 03:56
Copy link
Member Author

@dhruvkb dhruvkb left a comment

Choose a reason for hiding this comment

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

I can't approve this PR so please accept ✅ .

@obulat
Copy link
Contributor

obulat commented Mar 2, 2022

I moved the nav and active-media stores to the /stores/ directory as is advised by the Pinia docs to differentiate them from Vuex modules.
I also renamed useNav to useNavStore, and added the navStore unit tests.
Now, I believe, this PR is ready to merge.

Copy link
Contributor

@sarayourfriend sarayourfriend left a comment

Choose a reason for hiding this comment

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

As mentioned by @zackkrida elsewhere there are errors in the app when trying to run it locally. None of the comments I added are blocking I don't think.

One thing I'd like to see is adding the two new stores to tsconfig.json (or updating them to be .ts). I tried this locally (converting to .ts) and there were only three errors to fix in active-media.ts which just came down to moving JSDoc parameter type annotations into TS annotations.

@@ -1,3 +1,6 @@
// WebStorm fix for `~` alias not working:
// https://intellij-support.jetbrains.com/hc/en-us/community/posts/115000771544-ESLint-does-not-work-with-webpack-import-resolver-in-2017-3
process.chdir(__dirname)
Copy link
Contributor

Choose a reason for hiding this comment

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

Wow! What a weird find.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, definitely. It seems that the bug reports are from 2018, but I'm having this problem now 🤷

nuxt.config.ts Outdated Show resolved Hide resolved
* Only the properties used by components are exported as refs.
* `status` is not used anywhere in the components.
*/
const { type, id, message } = toRefs(state)
Copy link
Contributor

Choose a reason for hiding this comment

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

I couldn't find any usages of activeMediaStore.type. id and message both have usages though.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think type will be used when we have video, and we can have an activeVideo which will need to be treated differently from activeAudio

Comment on lines 58 to 60
type,
id,
message,
Copy link
Contributor

Choose a reason for hiding this comment

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

Should these all be readonly as well to prevent modifying them without going through the action functions?

src/stores/nav.js Outdated Show resolved Hide resolved
Comment on lines 9 to 28
export const useNavStore = defineStore(NAV, () => {
const state = reactive({
isEmbedded: true,
isReferredFromCc: false,
})
const { isEmbedded, isReferredFromCc } = toRefs(state)

function setIsEmbedded(isEmbedded = true) {
state.isEmbedded = isEmbedded
}
function setIsReferredFromCc(isReferredFromCc = true) {
state.isReferredFromCc = isReferredFromCc
}
return {
isEmbedded,
isReferredFromCc,
setIsEmbedded,
setIsReferredFromCc,
}
})
Copy link
Contributor

Choose a reason for hiding this comment

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

It looks like we use a mix of updating the isEmbedded and isReferredFromCc values directly and also through the setter functions. Should we standardize on one approach for simple properties like this?

navStore.setIsReferredFromCc(false)

navStore.isReferredFromCc = query.referrer.includes('creativecommons.org')

Copy link
Contributor

Choose a reason for hiding this comment

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

I prefer setter functions because they are easier to observe.

Copy link
Member

Choose a reason for hiding this comment

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

If we went with setter functions is there a way to enforce their usage, like to make isReferredFromCc readonly?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes! I'll do that


describe('Active Media Store', () => {
beforeEach(() => {
setActivePinia(createPinia())
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this have the effect of resetting the store before each test?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes:
// creates a fresh pinia and make it active so it's automatically picked
// up by any useStore() call without having to pass it to it:
// useStore(pinia)

@zackkrida
Copy link
Member

Reconfirming that this looks good! LGTM.

# Conflicts:
#	src/components/VFilters/VFilterChecklist.vue
#	src/locales/po-files/openverse.pot
@obulat obulat merged commit 912f1c4 into main Mar 3, 2022
@obulat obulat deleted the pinia branch March 3, 2022 03:43
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
💻 aspect: code Concerns the software code in the repository 🌟 goal: addition Addition of new feature 🟨 priority: medium Not blocking but should be addressed soon
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Set up Pinia
5 participants