Framework Migration - sgml/signature GitHub Wiki

The fact is that when node.js came along, JavaScript replaced Java and C#, but this led to things like classes in ES6, dependency injection in Angular.js, decorators in Ember, CSS-in-JS, ecosystem lock-in, engine-lock-in, etc, etc.

Resources

Safe Subsetting

frameworks:
  - name: "Hyperapp"
    year: 2017
    url: "https://hyperapp.dev"
    description: "A minimalist framework focused on simplicity and functional programming, usable directly in the browser."
  - name: "HTM"
    year: 2018
    url: "https://github.com/developit/htm"
    description: "A lightweight library for JSX-like syntax without compilation, compatible with frameworks like Preact."
  - name: "Lit"
    year: 2018
    url: "https://lit.dev"
    description: "A library for building web components using native browser APIs, without requiring a build process."
  - name: "Alpine.js"
    year: 2019
    url: "https://alpinejs.dev"
    description: "A lightweight framework for declarative, reactive UI, easily usable without Node.js or compilation."
  - name: "Petite-Vue"
    year: 2021
    url: "https://github.com/vuejs/petite-vue"
    description: "A minimalist subset of Vue.js, focused on progressive enhancement and browser-native use."

Freelancing

Product Roadmapping

Concrete Abstractions (Commoditized Features) Leaky Abstractions (Custom Code Requirements)
Basic components like buttons, modals, and forms in no-code tools (React, Vue, Svelte, Solid.js). Advanced custom components with specific business logic or unique interactivity.
General UI templates or themes provided by no-code tools. Highly customized designs requiring precise styling or animations.
Simple state management for basic applications. Complex state management involving multiple data sources or asynchronous actions.
Standard API integrations (e.g., connecting to common services like Stripe or Twilio). Custom API integrations with non-standard protocols or authentication mechanisms.
Standardized data visualization widgets (abstracting D3.js-like capabilities). Creating bespoke, domain-specific data visualizations.
Drag-and-drop functionality for building dashboards or reports. Performance optimizations for large-scale dynamic applications.
Responsive layouts generated automatically by frameworks/tools. Tailored responsiveness for niche devices or screen configurations.
Built-in routing for simple SPAs. Advanced routing mechanisms (e.g., conditional or nested routing).
One-size-fits-all performance tuning tools. Low-level performance tuning, like optimizing rendering or memory usage.

User Stories

Step Description Reference
Nuxt 2 to Nuxt Bridge Upgrade Nuxt 2 to the latest version, install Nuxt Bridge, update nuxt.config.js, and change commands to use nuxt2. Nuxt Bridge Overview
Nuxt Bridge to Nuxt 3 Install Nuxt 3, update nuxt.config.js to use Nuxt 3 features, and migrate plugins and middleware to the new format. Nuxt 3 Migration Overview
Webpack-Dev-Server to Nitro Replace webpack-dev-server with Nitro by updating the build and server configuration in nuxt.config.js. What's New With Nuxt 3
Vuex to Pinia Install Pinia, restructure Vuex modules into Pinia stores, and update state management logic. Migrating from Vuex to Pinia
Webpack to Vite Install Vite, update package.json scripts, replace webpack.config.js with vite.config.js, and configure Vite plugins. Migrating from Webpack to Vite
Babel to Esbuild Install Esbuild, update build scripts to use Esbuild, and configure Esbuild options in vite.config.js. Replacing Babel with Esbuild
Jest to Vitest Install Vitest, update test configuration files, and modify package.json scripts to use Vitest. Convert Jest to Vitest

Framework Agnostic Component Libraries

Decoupling a component from Nuxt to Vue for JSFiddle

# Step 1: Extract the Vue Component
component:
  file: MyComponent.vue
  content: |
    <template>
      <div>
        <label for="wiki-select">Choose an article:</label>
        <select id="wiki-select" v-model="selectedOption">
          <option v-for="option in options" :key="option" :value="option">{{ option }}</option>
        </select>
        <p>Selected option: {{ selectedOption }}</p>
      </div>
    </template>

    <script>
    export default {
      props: {
        options: {
          type: Array,
          required: true
        },
        selectedOption: {
          type: String,
          default: null
        }
      },
      methods: {
        foo(selectedOption) {
          console.log('Selected option changed:', selectedOption);
        }
      }
    };
    </script>

# Step 2: Extract the Vuex Store
store:
  file: store.js
  content: |
    import Vue from 'vue';
    import Vuex from 'vuex';
    import axios from 'axios';

    Vue.use(Vuex);

    export default new Vuex.Store({
      state: {
        options: [],
        selectedOption: null
      },
      mutations: {
        setOptions(state, options) {
          state.options = options;
        },
        setSelectedOption(state, option) {
          state.selectedOption = option;
        }
      },
      actions: {
        fetchOptions({ commit }) {
          const script = document.createElement('script');
          const callbackName = 'foo';
          
          window[callbackName] = (response) => {
            commit('setOptions', response.query.search.map(item => item.title));
          };

          const url = `https://en.wikipedia.org/w/api.php?action=query&format=json&list=search&srsearch=vue&origin=*&callback=${callbackName}`;
          script.src = url;
          document.body.appendChild(script);
          script.onload = () => {
            document.body.removeChild(script);
            delete window[callbackName];
          };
          script.onerror = (error) => {
            console.error('Error fetching options:', error);
            document.body.removeChild(script);
            delete window[callbackName];
          };
        }
      }
    });

# Step 3: Create the Main Entry File
main:
  file: main.js
  content: |
    import Vue from 'vue';
    import App from './MyComponent.vue';
    import store from './store';

    new Vue({
      store,
      render: h => h(App)
    }).$mount('#app');

# Step 4: Create an HTML File
html:
  file: index.html
  content: |
    <!DOCTYPE html>
    <html>
    <head>
      <meta charset="UTF-8">
      <title>Standalone Vue Component with Vuex</title>
      <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
      <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vuex.js"></script>
      <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
      <script src="./main.js" defer></script>
    </head>
    <body>
      <div id="app"></div>
    </body>
    </html>

# Summary
summary: |
  By following these steps, you can decouple a Vue component with a Vuex store from a Nuxt.js project and package it for use in a GitHub Gist. This process involves extracting the component and store, creating a main entry file, and setting up an HTML file to load the application.

Vue2 to Vue3

lifecycle_methods:
  mounting:
    vue2: 'mounted'
    vue3: 'onMounted'
  updating:
    vue2: 'updated'
    vue3: 'onUpdated'
  unmounting:
    vue2: 'beforeDestroy'
    vue3: 'onBeforeUnmount'
  before_mount:
    vue2: 'beforeMount'
    vue3: 'onBeforeMount'
  before_update:
    vue2: 'beforeUpdate'
    vue3: 'onBeforeUpdate'
  destroy:
    vue2: 'destroyed'
    vue3: 'onUnmounted'
vue_lifecycle_methods:
  vue2:
    beforeCreate: 
      description: "Called synchronously after the instance has been initialized, before data observation and event/watcher setup."
    created:
      description: "Called synchronously after the instance is created. At this point, the instance has finished processing options, which means the following have been set up: data observation, computed properties, methods, and watchers, though it has not yet been mounted to the DOM."
    beforeMount:
      description: "Called right before the mounting begins: the render function is about to be called for the first time."
    mounted:
      description: "Called after the instance has been mounted, where el is replaced by the newly created vm.$el. If the root instance is mounted to an in-document element, vm.$el will also be in-document when mounted is called."
    beforeUpdate:
      description: "Called when data changes, before the DOM is patched."
    updated:
      description: "Called after a data change causes the DOM to be re-rendered and patched."
    activated:
      description: "Called when a kept-alive component is activated."
    deactivated:
      description: "Called when a kept-alive component is deactivated."
    beforeDestroy:
      description: "Called right before a Vue instance is destroyed."
    destroyed:
      description: "Called after a Vue instance has been destroyed."
  vue3:
    beforeCreate:
      description: "Called right after the instance has been initialized, before data observation and event/watcher setup."
    created:
      description: "Called after the instance is created. At this point, the instance has finished processing options, which means the following have been set up: reactive data, computed properties, methods, and watchers, though it has not yet been mounted."
    beforeMount:
      description: "Called right before the mounting begins: the render function is about to be called for the first time."
    mounted:
      description: "Called after the instance has been mounted. At this point, the el property is replaced by the newly created vm.$el. If the root instance is mounted on an in-document element, vm.$el will also be in-document."
    beforeUpdate:
      description: "Called when data changes, before the virtual DOM is patched."
    updated:
      description: "Called after a data change causes the virtual DOM to be re-rendered and patched."
    beforeUnmount:
      description: "Called right before a component instance is unmounted."
    unmounted:
      description: "Called after a component instance is unmounted."
    activated:
      description: "Called when a kept-alive component is activated."
    deactivated:
      description: "Called when a kept-alive component is deactivated."
    errorCaptured:
      description: "Called when an error from a descendant component is captured."

Vuex to Pinia

lifecycle_methods:
  store_initialization:
    vuex: 'store'
    pinia: 'createPinia'
  state_definition:
    vuex: 'state'
    pinia: 'state'
  getters:
    vuex: 'getters'
    pinia: 'getters'
  actions:
    vuex: 'actions'
    pinia: 'actions'
  mutations:
    vuex: 'mutations'
    pinia: 'no direct equivalent (use actions or composition API)'
  modules:
    vuex: 'modules'
    pinia: 'no direct equivalent (use multiple stores)'
  plugins:
    vuex: 'plugins'
    pinia: 'plugins'
  state_access:
    vuex: 'this.$store.state'
    pinia: 'useStore().$state'
  commit:
    vuex: 'this.$store.commit'
    pinia: 'useStore().$patch'
  dispatch:
    vuex: 'this.$store.dispatch'
    pinia: 'useStore().$patch or direct action call'
  strict_mode:
    vuex: 'strict'
    pinia: 'no direct equivalent (use devtools or custom logic)'
  hot_module_replacement:
    vuex: 'store.hotUpdate'
    pinia: 'not needed (store is reactive by default)'

Vue2

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Vue with Vuex and Axios</title>
  <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vuex.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
</head>
<body>
  <div id="app">
    <dropdown-component></dropdown-component>
  </div>

  <script>
    // Vuex Store
    const store = new Vuex.Store({
      state: {
        options:  [
    { question: 'onboarding_sec_ques_born_city' },
    { question: 'onboarding_sec_ques_first_street' },
    { question: 'onboarding_sec_ques_oldest_cousin' },
    { question: 'onboarding_sec_ques_partner_meet' },
    { question: 'onboarding_sec_ques_first_pet' },
    { question: 'onboarding_sec_ques_childhood_hero' },
    { question: 'onboarding_sec_ques_fav_movie' },
    { question: 'onboarding_sec_ques_city_mom' },
    { question: 'onboarding_sec_ques_heirloom' },
    { question: 'onboarding_sec_ques_city_dad' },
  ],
        selectedOption: null,
        selectedOption2: null,
      },
      mutations: {
        setOptions(state, options) {
          state.options = options;
        },
        setSelectedOption(state, option) {
          state.selectedOption = option;
        },
        setSelectedOption2(state, option) {
          state.selectedOption2 = option;
        }
      },
      actions: {
        fetchOptions({ commit }) {
          const script = document.createElement('script');
          const callbackName = 'foo';
          
          // Define the callback function
          window[callbackName] = function(response) {
            const options = response.query.search.map(item => item.title);
            commit('setOptions', options);
          };

          // Set the JSONP URL
          const url = `https://en.wikipedia.org/w/api.php?action=query&format=json&list=search&srsearch=vue&origin=*&callback=${callbackName}`;
          
          // Set the script element's src to the JSONP URL
          script.src = url;
          
          // Append the script to the document
          document.body.appendChild(script);

          // Clean up the script and callback after execution
          script.onload = () => {
            document.body.removeChild(script);
            delete window[callbackName];
          };
          script.onerror = (error) => {
            console.error('Error fetching options:', error);
            document.body.removeChild(script);
            delete window[callbackName];
          };
        }
      }
    });

    // Vue Component
    Vue.component('dropdown-component', {
      template: `
        <div>
          <label for="wiki-select">Choose an article:</label>
          <select id="wiki-select" :value="selectedOption" @input="setSelectedOption($event.target.value)" ref="selectDropdown">
            <option v-for="option in options" :key="option.question" :value="option.question">{{ option.question }}</option>
          </select>
          <p>Selected option: {{ selectedOption }}</p>
          <label for="wiki-select2">Choose an article2:</label>
          <select id="wiki-select2" :value="selectedOption2" @input="setSelectedOption2($event.target.value)" ref="selectDropdown2">
            <option v-for="option in options" :key="option.question" :value="option.question">{{ option.question }}</option>
          </select>
          <p>Selected option2: {{ selectedOption2 }}</p>

        </div>
      `,
      computed: {
        ...Vuex.mapState(['options', 'selectedOption', 'selectedOption2']),
      },
      methods: {
        ...Vuex.mapMutations(['setSelectedOption','setSelectedOption2']),
        foo(selectedOption) {
          console.log('Selected option changed:', selectedOption);
        }
      },
      mounted() {
        //this.$store.dispatch('fetchOptions');
        this.$refs.selectDropdown.addEventListener('change', (event) => {
          this.foo(event.target.value);
        });
        this.$refs.selectDropdown2.addEventListener('change', (event) => {
          this.foo(event.target.value);
        });
      },
      beforeDestroy() {
        this.$refs.selectDropdown.removeEventListener('change', this.foo);
        this.$refs.selectDropdown2.removeEventListener('change', this.foo);
      }
    });

    // Vue Instance
    new Vue({
      el: '#app',
      store
    });
  </script>
</body>
</html>

Vue3

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Vue 3 with Pinia and Axios</title>
  <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
  <script src="https://unpkg.com/pinia@2/dist/pinia.iife.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
</head>
<body>
  <div id="app">
    <dropdown-component></dropdown-component>
  </div>

  <script>
    const { createApp, ref, onMounted, defineComponent } = Vue;
    const { createPinia, defineStore } = Pinia;

    // Define the Pinia store
    const useStore = defineStore('main', {
      state: () => ({
        options: [],
        selectedOption: null
      }),
      actions: {
        fetchOptions() {
          const script = document.createElement('script');
          const callbackName = 'foo';
          
          // Define the callback function
          window[callbackName] = (response) => {
            this.options = response.query.search.map(item => item.title);
          };

          // Set the JSONP URL
          const url = `https://en.wikipedia.org/w/api.php?action=query&format=json&list=search&srsearch=vue&origin=*&callback=${callbackName}`;
          
          // Set the script element's src to the JSONP URL
          script.src = url;
          
          // Append the script to the document
          document.body.appendChild(script);

          // Clean up the script and callback after execution
          script.onload = () => {
            document.body.removeChild(script);
            delete window[callbackName];
          };
          script.onerror = (error) => {
            console.error('Error fetching options:', error);
            document.body.removeChild(script);
            delete window[callbackName];
          };
        }
      }
    });

    // Define the Vue component
    const DropdownComponent = defineComponent({
      template: `
        <div>
          <label for="wiki-select">Choose an article:</label>
          <select id="wiki-select" v-model="selectedOption" ref="selectDropdown">
            <option v-for="option in options" :key="option" :value="option">{{ option }}</option>
          </select>
          <p>Selected option: {{ selectedOption }}</p>
        </div>
      `,
      setup() {
        const store = useStore();
        const selectDropdown = ref(null);

        // Define the foo method
        const foo = (selectedOption) => {
          console.log('Selected option changed:', selectedOption);
        };

        onMounted(() => {
          store.fetchOptions();
          selectDropdown.value.addEventListener('change', (event) => {
            foo(event.target.value);
          });
        });

        return {
          options: store.options,
          selectedOption: store.$state.selectedOption,
          selectDropdown
        };
      }
    });

    // Create the Vue app and Pinia store
    const app = createApp({
      components: {
        DropdownComponent
      }
    });

    const pinia = createPinia();
    app.use(pinia);
    app.mount('#app');
  </script>
</body>
</html>

Nuxt

/*
Scope:
    publicRuntimeConfig: These configuration values are accessible on both the server and the client. This means any value defined here can be accessed and used in your client-side code.

    privateRuntimeConfig: These configuration values are only accessible on the server side. They are not exposed to the client, making them suitable for sensitive data that should not be exposed to the client.

Security:

    publicRuntimeConfig: Since values here are available on the client side, they should not contain sensitive information such as API keys or secrets.

    privateRuntimeConfig: Safe to store sensitive information as these values are only accessible on the server.

Access:

    publicRuntimeConfig: You can access these values using this.$config in your Vue components and context.$config in the Nuxt context.

    privateRuntimeConfig: These values are accessible in server-side code, such as server middleware, API routes, and server-side Vue components.
*/

import axios from 'axios';

export default async () => {
  const { data } = await axios.get('https://api.example.com/config');

  return {
    publicRuntimeConfig: {
      apiBase: process.env.API_BASE || data.apiBase,
    },
    privateRuntimeConfig: {
      apiSecret: process.env.API_SECRET || data.apiSecret,
    },
  };
};

Vue.js

Vue3

JSFiddle

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Vue 3, Pinia, Axios, and Vue Router</title>
    <!-- Load Vue 3 via CDN -->
    <script src="https://unpkg.com/vue@next"></script>
    <!-- Load Vue Router via CDN -->
    <script src="https://unpkg.com/vue-router@next"></script>
    <!-- Load Pinia via CDN -->
    <script src="https://unpkg.com/pinia@next"></script>
    <!-- Load Axios via CDN -->
    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
</head>
<body>
    <div id="app"></div>
    <!-- Your custom JavaScript -->
    <script type="module">
        // Create a Pinia store with a variable foo
        import { createPinia, defineStore } from 'https://unpkg.com/pinia@next';
        import { createRouter, createWebHistory } from 'https://unpkg.com/vue-router@next';
        import { createApp, ref } from 'https://unpkg.com/vue@next';

        const useStore = defineStore('main', {
            state: () => ({
                foo: 'Hello from Pinia store!'
            })
        });

        // Composition API hook with a variable bar
        function useBar() {
            const bar = ref('Hello from Composition API hook!');
            return { bar };
        }

        // Define Vue components
        const HomeComponent = {
            template: '<div>Home Component</div>'
        };
        
        const AboutComponent = {
            template: '<div>About Component</div>'
        };

        // Set up Vue Router
        const routes = [
            { path: '/', component: HomeComponent },
            { path: '/about', component: AboutComponent }
        ];
        
        const router = createRouter({
            history: createWebHistory(),
            routes
        });

        // Create Vue app
        const app = createApp({
            setup() {
                const store = useStore();
                const { bar } = useBar();

                return { store, bar };
            },
            template: `
                <div>
                    <nav>
                        <router-link to="/">Home</router-link>
                        <router-link to="/about">About</router-link>
                    </nav>
                    <router-view></router-view>
                    <h1>{{ store.foo }}</h1>
                    <h1>{{ bar }}</h1>
                </div>
            `
        });

        // Use Pinia and Router
        app.use(createPinia());
        app.use(router);

        // Mount app
        app.mount('#app');
    </script>
</body>
</html>

Vue2

JSFiddle

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Vue 2, Vue Router, Vuex, and Axios</title>
    <!-- Load Vue 2 via CDN -->
    <script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
    <!-- Load Vue Router via CDN -->
    <script src="https://cdn.jsdelivr.net/npm/vue-router@3"></script>
    <!-- Load Vuex via CDN -->
    <script src="https://cdn.jsdelivr.net/npm/vuex@3"></script>
    <!-- Load Axios via CDN -->
    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
</head>
<body>
    <div id="app"></div>
    <!-- Your custom JavaScript -->
    <script>
        // Create a Vuex store
        const store = new Vuex.Store({
            state: {
                foo: 'Hello from Vuex store!'
            },
            mutations: {
                setFoo(state, newFoo) {
                    state.foo = newFoo;
                }
            },
            actions: {
                fetchFoo({ commit }) {
                    axios.get('https://jsonplaceholder.typicode.com/posts/1')
                        .then(response => {
                            commit('setFoo', response.data.title);
                        });
                }
            }
        });

        // Define Vue components
        const HomeComponent = {
            template: '<div>Home Component</div>'
        };
        
        const AboutComponent = {
            template: '<div>About Component</div>'
        };

        // Set up Vue Router
        const routes = [
            { path: '/', component: HomeComponent },
            { path: '/about', component: AboutComponent }
        ];
        
        const router = new VueRouter({
            routes
        });

        // Create Vue app
        new Vue({
            el: '#app',
            store,
            router,
            data() {
                return {
                    bar: 'Hello from Composition API hook!'
                };
            },
            template: `
                <div>
                    <nav>
                        <router-link to="/">Home</router-link>
                        <router-link to="/about">About</router-link>
                    </nav>
                    <router-view></router-view>
                    <h1>{{ $store.state.foo }}</h1>
                    <h1>{{ bar }}</h1>
                </div>
            `,
            created() {
                this.$store.dispatch('fetchFoo');
            }
        });
    </script>
</body>
</html>

Github Issue Details

documentation:
  - name: "GitHub Issue - Extract window.__NUXT__ to <script>"
    description: "Discusses how to extract `window.__NUXT__` to a `<script>` tag for SEO purposes."
    url: "https://github.com/nuxt/nuxt/issues/8548"
    
  - name: "GitHub Issue - Remove window.__NUXT__ once app initialises"
    description: "Discusses the use of `window.__NUXT__` for backward compatibility and its removal in future versions."
    url: "https://github.com/nuxt/nuxt/issues/25336"

  - name: "Nuxt Documentation - The Context"
    description: "Explains the context object, which includes `window.__NUXT__`."
    url: "https://v2.nuxt.com/docs/internals-glossary/context/"

Window Object Keys

Framework Specific Object Literals:

Framework Window Object Key(s)
Nuxt __nuxt__
Vue 2 Vue, Vuex
Vue 3 Vue, Vuex
Angular.js $window
Angular $window, ng, ngCore
Ember Em, Ember
Svelte svelte, Svelte
Solid.js solid
Mithril.js m, Mithril
Backbone.js Backbone, _, $

DOM Wrappers

event_bubbling_issues:
  Ember:
    - description: "Event bubbling issues with components not propagating events as expected."
      url: https://stackoverflow.com/questions/19776520/bubble-up-events-from-ember-component
    - description: "Stopping bubbling/propagation in Ember 2.x causing unexpected behavior."
      url: https://stackoverflow.com/questions/48713114/stopping-bubbling-propagation-in-ember-2-x
    - description: "Inner actions bubbling to outer link-to helpers unexpectedly."
      url: https://stackoverflow.com/questions/35157428/how-to-stop-the-inner-action-from-bubbling-to-the-outer-link-to-helper
    - description: "View events bubbling to actions in Ember."
      url: https://github.com/emberjs/ember.js/issues/10040

  Vue2:
    - description: "Custom events not bubbling naturally in Vue 2."
      url: https://stackoverflow.com/questions/41993508/vuejs-bubbling-custom-events
    - description: "Preventing event bubbling in Vue 2 using modifiers like `.stop`."
      url: https://stackoverflow.com/questions/48798216/prevent-event-bubbling-in-vue
    - description: "Challenges with bubbling events on component chains."
      url: https://stackoverflow.com/questions/42029150/how-to-bubble-events-on-a-component-subcomponent-chain-with-vue-js-2

  Vue3:
    - description: "Custom events do not propagate up the component chain by default."
      url: https://stackoverflow.com/questions/64613446/do-custom-events-propagate-up-the-component-chain-in-vue-3
    - description: "Issues with event bubbling in SVG elements within Vue 3 components."
      url: https://www.devgem.io/posts/handling-click-events-in-vue-3-solving-svg-click-event-bubbling
    - description: "Opt-in event bubbling discussion for Vue 3."
      url: https://github.com/vuejs/rfcs/discussions/581

  Svelte:
    - description: "Event bubbling issues with wrong order of event handling."
      url: https://github.com/sveltejs/svelte/issues/14787
    - description: "Event bubbling not working as expected in nested components."
      url: https://github.com/sveltejs/svelte/issues/149
    - description: "Event bubbling order issues in Svelte 5."
      url: https://github.com/sveltejs/svelte/issues/14786

  Solid.js:
    - description: "stopPropagation on parent elements affecting child event handling."
      url: https://github.com/solidjs/solid/issues/1278
    - description: "Event delegation and bubbling behavior in Solid.js."
      url: https://docs.solidjs.com/reference/jsx-attributes/on_
    - description: "Event handlers and bubbling behavior in Solid.js."
      url: https://docs.solidjs.com/concepts/components/event-handlers

  Lit.js:
    - description: "LitElement prevents event bubbling on polyfilled browsers."
      url: https://github.com/lit/lit-element/issues/658
    - description: "Change events not bubbling outside Shadow DOM in Lit."
      url: https://github.com/lit/lit-element/issues/922
    - description: "Declarative event listeners and bubbling in Lit."
      url: https://lit.dev/docs/components/events/

DOM Events

<!-- Vue 1-->
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Vue 1 Select Binding with v-bind</title>
  <script src="https://cdn.jsdelivr.net/vue/1.0.28/vue.min.js"></script>
</head>
<body>
  <div id="app">
    <select :value="selectedOption" @change="updateSelectedOption($event)">
      <option v-for="option in options" :value="option">{{ option }}</option>
    </select>
    <p>Selected option: {{ selectedOption }}</p>
  </div>

  <script>
    new Vue({
      el: '#app',
      data: {
        options: ['Option 1', 'Option 2', 'Option 3'],
        selectedOption: ''
      },
      methods: {
        updateSelectedOption(event) {
          this.selectedOption = event.target.value;
        }
      }
    });
  </script>
</body>
</html>
<!-- Vue2 -->
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Vue 2 Select Binding</title>
  <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
</head>
<body>
  <div id="app">
    <select v-model="selectedOption" ref="selectElement">
      <option v-for="option in options" :key="option" :value="option">{{ option }}</option>
    </select>
    <p>Selected option: {{ selectedOption }}</p>
  </div>

  <script>
    new Vue({
      el: '#app',
      data: {
        options: ['Option 1', 'Option 2', 'Option 3'],
        selectedOption: ''
      },
      mounted() {
        this.$refs.selectElement.addEventListener('input', this.onInputChange);
      },
      methods: {
        onInputChange(event) {
          this.selectedOption = event.target.value;
        }
      }
    });
  </script>
</body>
</html>
<!--Options API-->
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Vue 3 Select Binding</title>
  <script src="https://unpkg.com/vue@next"></script>
</head>
<body>
  <div id="app">
    <select v-model="selectedOption" ref="selectElement">
      <option v-for="option in options" :key="option" :value="option">{{ option }}</option>
    </select>
    <p>Selected option: {{ selectedOption }}</p>
  </div>

  <script>
    const app = Vue.createApp({
      data() {
        return {
          options: ['Option 1', 'Option 2', 'Option 3'],
          selectedOption: ''
        };
      },
      mounted() {
        this.$refs.selectElement.addEventListener('input', this.onInputChange);
      },
      methods: {
        onInputChange(event) {
          this.selectedOption = event.target.value;
        }
      }
    }).mount('#app');
  </script>
</body>
</html>
<!-- jQuery -->
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>jQuery Select Binding</title>
  <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</head>
<body>
  <select id="selectElement">
    <option value="Option 1">Option 1</option>
    <option value="Option 2">Option 2</option>
    <option value="Option 3">Option 3</option>
  </select>
  <p id="selectedOption">Selected option: </p>

  <script>
    $(document).ready(function() {
      $('#selectElement').on('input', function() {
        $('#selectedOption').text('Selected option: ' + $(this).val());
      });
    });
  </script>
</body>
</html>
<!--Composition API-->
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Vue 3 Select Binding (Composition API)</title>
  <script src="https://unpkg.com/vue@next"></script>
</head>
<body>
  <div id="app">
    <select ref="selectElement">
      <option v-for="option in options" :key="option" :value="option">{{ option }}</option>
    </select>
    <p>Selected option: {{ selectedOption }}</p>
  </div>

  <script>
    const { ref, onMounted } = Vue;

    const app = Vue.createApp({
      setup() {
        const options = ref(['Option 1', 'Option 2', 'Option 3']);
        const selectedOption = ref('');
        const selectElement = ref(null);

        onMounted(() => {
          selectElement.value.addEventListener('input', onInputChange);
        });

        const onInputChange = (event) => {
          selectedOption.value = event.target.value;
        };

        return { options, selectedOption, selectElement };
      }
    }).mount('#app');
  </script>
</body>
</html>

URLSearchParams Method Definition and Test

jQuery

Plain JS

function getQueryParam(key) {
    const urlParams = new URLSearchParams(window.location.search);
    return urlParams.get(key);
}

jQuery Usage

$(document).ready(function() {
    // Store the function object
    $(document).data('getQueryParam', getQueryParam);

    // Retrieve and use the function
    const getQueryParam = $(document).data('getQueryParam');
    const value = getQueryParam('foo');
    
    // Assert the query parameter
    if (value === 'bar') {
        console.log("The query parameter 'foo' has the value 'bar'");
    } else {
        console.log("The query parameter 'foo' does not have the value 'bar'");
    }
});

JQuery QUnit Test

QUnit.test("Query string contains key 'foo' with value 'bar'", function(assert) { 
    const getQueryParam = $(document).data('getQueryParam'); 
    const value = getQueryParam('foo'); 
    assert.equal(value, 'bar', "The query parameter 'foo' should have the value 'bar'"); });

Angular

Angular Service

// query-params.service.ts
import { Injectable } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

@Injectable({
  providedIn: 'root',
})
export class QueryParamsService {
  constructor(private route: ActivatedRoute) {}

  getQueryParam(key: string): string | null {
    return this.route.snapshot.queryParamMap.get(key);
  }
}

Jest Test

// query-params.service.spec.ts
import { Injector } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { QueryParamsService } from './query-params.service';

describe('QueryParamsService', () => {
  let injector: Injector;
  let service: QueryParamsService;

  beforeEach(() => {
    injector = Injector.create({
      providers: [
        QueryParamsService,
        {
          provide: ActivatedRoute,
          useValue: {
            snapshot: {
              queryParamMap: {
                get: (key: string) => (key === 'foo' ? 'bar' : null),
              },
            },
          },
        },
      ],
    });
    service = injector.get(QueryParamsService);
  });

  it("should have query parameter 'foo' with value 'bar'", () => {
    const value = service.getQueryParam('foo');
    expect(value).toBe('bar');
  });
});

Python Mapping for Vue2 to Vue3

import os

# Mapping of Vue 2 syntax to Vue 3 syntax
migration_dict = {
    'data() {': 'setup() {\n    const state = reactive({\n',
    'methods: {': 'methods = {\n',
    'this.': 'state.',
    '};\n  },': '};\n\n    return { ...toRefs(state), ...methods };\n  }',
    'export default {': 'import { reactive, toRefs } from \'vue\';\n\nexport default {'
}

def migrate_vue2_to_vue3(file_path):
    with open(file_path, 'r', encoding='utf-8') as file:
        vue2_content = file.read()

    vue3_content = vue2_content
    for vue2_syntax, vue3_syntax in migration_dict.items():
        vue3_content = vue3_content.replace(vue2_syntax, vue3_syntax)

    vue3_content = f"<template>\n" + \
                   vue3_content.split('<template>')[1].split('</template>')[0] + \
                   f"\n</template>\n" + \
                   f"<script>\n" + \
                   vue3_content.split('<script>')[1].split('</script>')[0] + \
                   f"\n</script>\n" + \
                   f"<style scoped>" + \
                   vue3_content.split('<style scoped>')[1].split('</style>')[0] + \
                   f"</style>"

    output_file_path = os.path.join(os.path.dirname(file_path), 'Counter-vue3.vue')
    with open(output_file_path, 'w', encoding='utf-8') as file:
        file.write(vue3_content)

    print(f"Migrated component saved to: {output_file_path}")

# Usage Example
file_path = os.path.join(os.getcwd(), 'Counter.vue')
migrate_vue2_to_vue3(file_path)

Test Util and elements

Component

// components/SelectComponent.vue
<template>
  <div>
    <label for="country">Choose a country:</label>
    <select id="country" v-model="selectedCountry">
      <option value="US">United States</option>
      <option value="CA">Canada</option>
      <option value="MX">Mexico</option>
    </select>
    <p>Selected country: {{ selectedCountry }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      selectedCountry: 'US',
    };
  },
};
</script>

<style scoped>
select {
  margin-top: 10px;
}
</style>

Test File

// tests/unit/SelectComponent.spec.js
import { mount } from '@vue/test-utils';
import SelectComponent from '@/components/SelectComponent.vue';

describe('SelectComponent', () => {
  test('selects a country', async () => {
    const wrapper = mount(SelectComponent);

    // Find the select element
    const select = wrapper.find('select');

    // Simulate changing the select value
    await select.setValue('CA');

    // Assert the selected country is updated
    expect(wrapper.vm.selectedCountry).toBe('CA');
    expect(wrapper.find('p').text()).toBe('Selected country: CA');
  });
});

Cookie Wrapper APIs

Framework Cookie API Last Release Date Author Documentation Notable Forks
AngularJS $cookies service April 7, 2022 Miško Hevery (Google) AngularJS Cookie API -
Dojo Toolkit dojo/cookie module January 20, 2021 Alex Russell, Dylan Schiemann Dojo Cookie API -
Ext JS (Sencha) Ext.util.Cookies class May 21, 2024 Jack Slocum Ext JS Cookie API bjornharrtell/extjs
YUI YAHOO.util.Cookie utility September 2014 Yahoo! YUI Cookie API yui/yui3, yuiphp/yui
SproutCore SC.Cookie class February 2, 2015 Charles Jolley (Sproutit, Apple Inc.) SproutCore Cookie API -
MooTools Cookie utility July 18, 2008 Valerio Proietti MooTools Cookie API -
AlloyUI A.Cookie utility October 2015 Liferay AlloyUI Cookie API -
Apache Royale LSO (Local Shared Objects) Ongoing Apache Software Foundation Apache Royale LSO Documentation -

Window and Navigator Events

SolidJS

import { createSignal, createEffect, onCleanup } from "solid-js";

function App() {
  // Reactive signals for window width and online status
  const [windowWidth, setWindowWidth] = createSignal(window.innerWidth);
  const [isOnline, setIsOnline] = createSignal(navigator.onLine);

  // Named functions for handling events
  function handleResizeEvent(event) {
    setWindowWidth(window.innerWidth); // Using the window directly as it updates dynamically
  }

  function handleOnlineEvent(event) {
    setIsOnline(true);
  }

  function handleOfflineEvent(event) {
    setIsOnline(false);
  }

  // Effect for tracking window resize
  function trackWindowResize() {
    window.addEventListener("resize", handleResizeEvent);
    onCleanup(removeWindowResizeTracker);
  }

  function removeWindowResizeTracker() {
    window.removeEventListener("resize", handleResizeEvent);
  }

  createEffect(trackWindowResize);

  // Effect for tracking online status
  function trackOnlineStatus() {
    window.addEventListener("online", handleOnlineEvent);
    window.addEventListener("offline", handleOfflineEvent);
    onCleanup(removeOnlineStatusTracker);
  }

  function removeOnlineStatusTracker() {
    window.removeEventListener("online", handleOnlineEvent);
    window.removeEventListener("offline", handleOfflineEvent);
  }

  createEffect(trackOnlineStatus);

  return (
    <div>
      <h1>Fine-Grained Reactivity Example</h1>
      <p>Window width: {windowWidth()}px</p>
      <p>Status: {isOnline() ? "Online" : "Offline"}</p>
    </div>
  );
}

export default App;

Template XSS

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Security Risks in Multiple Frameworks</title>
  <!-- CDN Links for Frameworks -->
  <script src="https://cdn.jsdelivr.net/npm/react/umd/react.development.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/react-dom/umd/react-dom.development.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.8.2/angular.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/@angular/[email protected]/bundles/core.umd.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/svelte/compiler.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/solid-js/dist/solid.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.2.37/vue.global.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/ember-source/dist/ember.prod.js"></script>
</head>
<body>
  <h1>Security Risks Demonstration</h1>

  <!-- React -->
  <div id="react"></div>
  <script>
    const ReactRoot = document.getElementById("react");
    const userInput = "<script>alert('React XSS')</script>";
    ReactDOM.render(
      React.createElement("div", null, userInput), // Vulnerable to XSS
      ReactRoot
    );
    // Related Issue: https://github.com/facebook/react/issues/3473
  </script>

  <!-- AngularJS -->
  <div ng-app>
    <div ng-bind-html="'<script>alert(`AngularJS XSS`)</script>'"></div>
    <!-- Related Issue: https://github.com/angular/angular.js/issues/1740 -->
  </div>

  <!-- Angular -->
  <script>
    // Angular setup and XSS example would require a full application bootstrap
    console.warn("Angular requires a full app bootstrap. Simulating here.");
    // Related Issue: https://angular.io/guide/security#security-contexts
  </script>

  <!-- Svelte -->
  <script>
    const svelteApp = document.createElement("div");
    document.body.appendChild(svelteApp);
    svelteApp.innerHTML = "<script>alert('Svelte XSS')</script>"; // Vulnerable
    // Related Issue: https://github.com/sveltejs/svelte/issues/3012
  </script>

  <!-- Solid.js -->
  <div id="solid"></div>
  <script>
    Solid.render(
      () => "<script>alert('Solid.js XSS')</script>", // Vulnerable
      document.getElementById("solid")
    );
    // Related Issue: https://github.com/solidjs/solid/issues/612
  </script>

  <!-- Vue 3 -->
  <div id="vue"></div>
  <script>
    const app = Vue.createApp({
      data() {
        return { content: "<script>alert('Vue 3 XSS')</script>" }; // Vulnerable
      },
      template: '<div v-html="content"></div>',
    });
    app.mount("#vue");
    // Related Issue: https://github.com/vuejs/core/issues/8985
  </script>

  <!-- Ember -->
  <div id="ember"></div>
  <script>
    const emberApp = document.createElement("div");
    emberApp.innerHTML = "<script>alert('Ember XSS')</script>"; // Vulnerable
    document.body.appendChild(emberApp);
    // Related Issue: https://github.com/emberjs/ember.js/issues/18755
  </script>
</body>
</html>

DOM ID to Store Mapping

Pinia

Store

import { defineStore } from 'pinia';

export const useDataStore = defineStore('dataStore', {
  state: () => ({
    elementData: {}, // Acts like the `.data()` storage
  }),
  actions: {
    setData(id, key, value) {
      if (!this.elementData[id]) {
        this.elementData[id] = {};
      }
      this.elementData[id][key] = value;
    },
    getData(id, key) {
      return this.elementData[id]?.[key];
    },
    removeData(id, key) {
      if (this.elementData[id]) {
        delete this.elementData[id][key];
        // Cleanup if no more keys
        if (Object.keys(this.elementData[id]).length === 0) {
          delete this.elementData[id];
        }
      }
    },
  },
});

Vue Component

<template>
  <div id="myElement" @click="addData">Click me!</div>
  <div id="anotherElement" @click="getData">Check Data!</div>
</template>

<script>
import { useDataStore } from '@/stores/dataStore';

export default {
  setup() {
    const store = useDataStore();

    const addData = () => {
      // Simulates storing data on the DOM element
      store.setData('myElement', 'exampleKey', 'exampleValue');
    };

    const getData = () => {
      const value = store.getData('myElement', 'exampleKey');
      console.log(value); // Logs "exampleValue"
    };

    return { addData, getData };
  },
};
</script>

CSS StyleManager OOP

tests:
  - name: "StyleManager Initialization Test"
    url: "https://github.com/apache/royale-asjs/blob/master/frameworks/projects/royale/test/unit/css/StyleManagerInitializationTests.as"
  - name: "StyleManager Registration Test"
    url: "https://github.com/apache/royale-asjs/blob/master/frameworks/projects/royale/test/unit/css/StyleManagerRegistrationTests.as"
  - name: "StyleManager Style Resolution Test"
    url: "https://github.com/apache/royale-asjs/blob/master/frameworks/projects/royale/test/unit/css/CSSStyleResolutionTests.as"
  - name: "CSS File Loading Test"
    url: "https://github.com/apache/royale-asjs/blob/master/frameworks/projects/royale/test/unit/css/CSSFileLoadingTests.as"
  - name: "CSS Parsing Test"
    url: "https://github.com/apache/royale-asjs/blob/master/frameworks/projects/royale/test/unit/css/CSSParserTests.as"
  - name: "CSS Inheritance Test"
    url: "https://github.com/apache/royale-asjs/blob/master/frameworks/projects/royale/test/unit/css/CSSInheritanceTests.as"
  - name: "Style Declaration Merging Test"
    url: "https://github.com/apache/royale-asjs/blob/master/frameworks/projects/royale/test/unit/css/StyleDeclarationMergingTests.as"
⚠️ **GitHub.com Fallback** ⚠️