From 18c8f88acbf7855326952ead3c6deb463f140546 Mon Sep 17 00:00:00 2001 From: Niklas Wolf Date: Mon, 17 Jun 2024 15:10:46 +0200 Subject: [PATCH 1/8] feat: add possibility to not load Mollie script globally --- package.json | 3 +- src/module.ts | 14 ++++++-- src/runtime/composables/useMollie.ts | 10 ++++-- src/runtime/plugins/plugin.client.ts | 53 +++++++++++++++++++++++----- src/runtime/plugins/plugin.server.ts | 21 ++++++----- src/types.d.ts | 8 +++-- 6 files changed, 84 insertions(+), 25 deletions(-) diff --git a/package.json b/package.json index e4011e0..6c9240c 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,8 @@ "prettier-check": "pnpm exec prettier . --check" }, "dependencies": { - "@nuxt/kit": "^3.8.2" + "@nuxt/kit": "^3.8.2", + "defu": "^6.1.4" }, "devDependencies": { "@nuxt/devtools": "latest", diff --git a/src/module.ts b/src/module.ts index b4c2173..63f1b77 100644 --- a/src/module.ts +++ b/src/module.ts @@ -1,15 +1,25 @@ import { defineNuxtModule, addPlugin, addImports, createResolver, addComponent } from '@nuxt/kit' import type { MollieOptions } from './types' +import { defu } from 'defu' export default defineNuxtModule({ meta: { name: 'nuxt-mollie-payments-components', - configKey: 'nuxtMolliePaymentsComponents', + configKey: 'molliePaymentsComponents', + }, + defaults: { + profileId: '', + defaultLocale: 'en_US', + testMode: false, + includeScriptGlobally: true, }, async setup(options, nuxt) { const resolver = createResolver(import.meta.url) - nuxt.options.runtimeConfig.public.mollie ||= options + nuxt.options.runtimeConfig.public.molliePaymentsComponents = defu( + nuxt.options.runtimeConfig.public.molliePaymentsComponents, + options, + ) addPlugin({ src: resolver.resolve('./runtime/plugins/plugin.server'), diff --git a/src/runtime/composables/useMollie.ts b/src/runtime/composables/useMollie.ts index 126bcc8..8eb9af9 100644 --- a/src/runtime/composables/useMollie.ts +++ b/src/runtime/composables/useMollie.ts @@ -1,4 +1,4 @@ -import { useNuxtApp } from '#imports' +import { useNuxtApp, ref } from '#imports' import type { CreateLocaleInstanceArgs } from '../../types' /** @@ -6,9 +6,14 @@ import type { CreateLocaleInstanceArgs } from '../../types' */ export function useMollie(args: CreateLocaleInstanceArgs = {}) { const { $mollie } = useNuxtApp() + const isInitialized = ref(false) async function init() { - $mollie.createMollieInstance(args) + // Wait for scripts to be loaded, then initialize the mollie instance. + await $mollie.scriptLoadedPromise.then(() => { + $mollie.createMollieInstance(args) + isInitialized.value = true + }) } async function getToken(): Promise { @@ -23,5 +28,6 @@ export function useMollie(args: CreateLocaleInstanceArgs = {}) { return { init, getToken, + isInitialized, } } diff --git a/src/runtime/plugins/plugin.client.ts b/src/runtime/plugins/plugin.client.ts index 14ef8c8..542e0ca 100644 --- a/src/runtime/plugins/plugin.client.ts +++ b/src/runtime/plugins/plugin.client.ts @@ -1,22 +1,58 @@ import { defineNuxtPlugin } from '#app' -import { useRuntimeConfig } from '#imports' -import type { CreateLocaleInstanceArgs, MolliePlugin, MollieOptions } from '../../types' +import { useHead, useRuntimeConfig } from '#imports' +import type { CreateLocaleInstanceArgs, MollieOptions, MolliePlugin } from '../../types' + +/** + * Get a promise with its resolvers. + * This can be replaced by native JS when browser support is better + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/withResolvers + */ +function getPromiseWithResolvers() { + let resolve: (value: T | PromiseLike) => void = () => {} + let reject: (value: T | PromiseLike) => void = () => {} + const promise = new Promise((res, rej) => { + resolve = res + reject = rej + }) + + return { promise, resolve, reject } +} export default defineNuxtPlugin({ name: 'mollie-instance', enforce: 'pre', async setup(nuxtApp) { - if (!window.Mollie) { - console.error( - "mollie-register plugin didn't register required scripts, thus mollie instance cannot be created.", - ) + /* + * Register mollie-script client-side for client-side rendering. + * If SSR is used, this will NOT register a second script, because it was already registered server side (see server-plugin) + */ + const { promise: scriptLoadedPromise, resolve } = getPromiseWithResolvers() + if (window.Mollie) { + resolve(true) + } else { + useHead({ + script: [ + { + src: 'https://js.mollie.com/v1/mollie.js', + defer: true, + onload() { + resolve(true) + }, + }, + ], + }) } - const runtimeConfig = useRuntimeConfig() + const runtimeConfig = useRuntimeConfig() const mollieOptions = runtimeConfig?.public?.mollie as MollieOptions function createLocaleInstance(args: CreateLocaleInstanceArgs) { + if (!window.Mollie) { + console.error( + "mollie-register plugin didn't register required scripts, thus mollie instance cannot be created.", + ) + } if (!args) { args = { profileId: mollieOptions.profileId, @@ -27,7 +63,7 @@ export default defineNuxtPlugin({ const { profileId, testMode, locale } = args - return profileId + return profileId && window.Mollie ? window.Mollie(profileId, { locale: locale ?? 'en_US', testmode: typeof testMode === 'boolean' ? testMode : true, @@ -41,6 +77,7 @@ export default defineNuxtPlugin({ this.mollieInstance = this.mollieInstance || createLocaleInstance(args) return this.mollieInstance }, + scriptLoadedPromise, } nuxtApp.provide('mollie', Mollie) diff --git a/src/runtime/plugins/plugin.server.ts b/src/runtime/plugins/plugin.server.ts index a002dc8..f0c2be2 100644 --- a/src/runtime/plugins/plugin.server.ts +++ b/src/runtime/plugins/plugin.server.ts @@ -1,18 +1,21 @@ import { defineNuxtPlugin } from '#app' -import { useServerHead } from '#imports' +import { useServerHead, useRuntimeConfig } from '#imports' export default defineNuxtPlugin({ name: 'mollie-register', enforce: 'pre', async setup() { - useServerHead({ - script: [ - { - src: 'https://js.mollie.com/v1/mollie.js', - defer: true, - }, - ], - }) + const config = useRuntimeConfig().public.molliePaymentsComponents + if (config.includeScriptGlobally) { + useServerHead({ + script: [ + { + src: 'https://js.mollie.com/v1/mollie.js', + defer: true, + }, + ], + }) + } }, }) diff --git a/src/types.d.ts b/src/types.d.ts index 3eb9426..ac8065d 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -23,13 +23,14 @@ export type CreateLocaleInstanceArgs = { declare global { interface Window { - Mollie: Mollie + Mollie?: Mollie } } export type MolliePlugin = { mollieInstance: ReturnType | null createMollieInstance(args: CreateLocaleInstanceArgs): void + scriptLoadedPromise: Promise } declare module '#app' { interface NuxtApp { @@ -82,6 +83,7 @@ export type MollieOptions = { profileId: string defaultLocale: MollieLocale testMode: boolean + includeScriptGlobally?: boolean } export type MollieConfig = { @@ -116,11 +118,11 @@ export type MollieCreditCardMandate = { declare module 'nuxt/schema' { interface NuxtConfig { - mollie?: MollieOptions + molliePaymentsComponents?: MollieOptions } interface PublicRuntimeConfig { - mollie: MollieOptions + molliePaymentsComponents: MollieOptions } } From 5e1a3672402b2996f27f124e70a32143378aaaf9 Mon Sep 17 00:00:00 2001 From: Niklas Wolf Date: Mon, 17 Jun 2024 15:35:23 +0200 Subject: [PATCH 2/8] feat: expose function from useMollie composable to load the Mollie script async --- playground/app.vue | 67 +-------------------------- playground/nuxt.config.ts | 4 +- playground/pages/index.vue | 68 ++++++++++++++++++++++++++++ playground/pages/without.vue | 36 +++++++++++++++ src/runtime/composables/useMollie.ts | 2 +- src/runtime/plugins/plugin.client.ts | 36 ++++++++------- src/types.d.ts | 2 +- 7 files changed, 130 insertions(+), 85 deletions(-) create mode 100644 playground/pages/index.vue create mode 100644 playground/pages/without.vue diff --git a/playground/app.vue b/playground/app.vue index 9fc4dbe..ea4ef5c 100644 --- a/playground/app.vue +++ b/playground/app.vue @@ -1,68 +1,3 @@ - - diff --git a/playground/nuxt.config.ts b/playground/nuxt.config.ts index f69c1b6..d80243b 100644 --- a/playground/nuxt.config.ts +++ b/playground/nuxt.config.ts @@ -1,4 +1,5 @@ export default defineNuxtConfig({ + ssr: true, extends: ['@shopware-pwa/composables-next/nuxt-layer'], modules: ['../src/module', '@shopware-pwa/nuxt3-module'], runtimeConfig: { @@ -10,10 +11,11 @@ export default defineNuxtConfig({ }, }, /* config not needed with mollie > v4.4.0 */ - mollie: { + molliePaymentsComponents: { defaultLocale: 'en_US', profileId: 'pfl_E5EmGZ98YT', testMode: true, + includeScriptGlobally: false, }, devtools: { enabled: true }, }) diff --git a/playground/pages/index.vue b/playground/pages/index.vue new file mode 100644 index 0000000..9fc4dbe --- /dev/null +++ b/playground/pages/index.vue @@ -0,0 +1,68 @@ + + + diff --git a/playground/pages/without.vue b/playground/pages/without.vue new file mode 100644 index 0000000..fa84479 --- /dev/null +++ b/playground/pages/without.vue @@ -0,0 +1,36 @@ + + + + + diff --git a/src/runtime/composables/useMollie.ts b/src/runtime/composables/useMollie.ts index 8eb9af9..79698a9 100644 --- a/src/runtime/composables/useMollie.ts +++ b/src/runtime/composables/useMollie.ts @@ -10,7 +10,7 @@ export function useMollie(args: CreateLocaleInstanceArgs = {}) { async function init() { // Wait for scripts to be loaded, then initialize the mollie instance. - await $mollie.scriptLoadedPromise.then(() => { + await $mollie.loadMollieScript().then(() => { $mollie.createMollieInstance(args) isInitialized.value = true }) diff --git a/src/runtime/plugins/plugin.client.ts b/src/runtime/plugins/plugin.client.ts index 542e0ca..2e2783a 100644 --- a/src/runtime/plugins/plugin.client.ts +++ b/src/runtime/plugins/plugin.client.ts @@ -23,25 +23,29 @@ export default defineNuxtPlugin({ enforce: 'pre', async setup(nuxtApp) { - /* + /** * Register mollie-script client-side for client-side rendering. * If SSR is used, this will NOT register a second script, because it was already registered server side (see server-plugin) */ - const { promise: scriptLoadedPromise, resolve } = getPromiseWithResolvers() - if (window.Mollie) { - resolve(true) - } else { - useHead({ - script: [ - { - src: 'https://js.mollie.com/v1/mollie.js', - defer: true, - onload() { - resolve(true) + function loadMollieScript() { + const { promise, resolve } = getPromiseWithResolvers() + if (window.Mollie) { + resolve(true) + } else { + useHead({ + script: [ + { + src: 'https://js.mollie.com/v1/mollie.js', + defer: true, + onload() { + resolve(true) + }, }, - }, - ], - }) + ], + }) + } + + return promise } const runtimeConfig = useRuntimeConfig() @@ -77,7 +81,7 @@ export default defineNuxtPlugin({ this.mollieInstance = this.mollieInstance || createLocaleInstance(args) return this.mollieInstance }, - scriptLoadedPromise, + loadMollieScript, } nuxtApp.provide('mollie', Mollie) diff --git a/src/types.d.ts b/src/types.d.ts index ac8065d..a8433ba 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -30,7 +30,7 @@ declare global { export type MolliePlugin = { mollieInstance: ReturnType | null createMollieInstance(args: CreateLocaleInstanceArgs): void - scriptLoadedPromise: Promise + loadMollieScript: () => Promise } declare module '#app' { interface NuxtApp { From 48924ebc894c5dd3006db23b8586c1cc7291e0ea Mon Sep 17 00:00:00 2001 From: Niklas Wolf Date: Mon, 17 Jun 2024 15:42:04 +0200 Subject: [PATCH 3/8] feat: adjust tests --- test/useMollie.test.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/useMollie.test.ts b/test/useMollie.test.ts index 57568cd..ea40fbb 100644 --- a/test/useMollie.test.ts +++ b/test/useMollie.test.ts @@ -4,6 +4,7 @@ import { useMollie } from './src/runtime/composables/useMollie' vi.mock('#imports', () => ({ useNuxtApp: vi.fn(), + ref: vi.fn((initialValue) => ({ value: initialValue })), })) describe('useMollie', () => { @@ -23,6 +24,7 @@ describe('useMollie', () => { mollieInstance: { createToken: mockCreateToken, }, + loadMollieScript: vi.fn().mockResolvedValue(true), }, } From 5a70daf677f7042999d4dc22fa9fde96cefc5347 Mon Sep 17 00:00:00 2001 From: Niklas Wolf Date: Mon, 17 Jun 2024 15:48:10 +0200 Subject: [PATCH 4/8] fix: use global loading in playground per default --- playground/nuxt.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playground/nuxt.config.ts b/playground/nuxt.config.ts index d80243b..daf4129 100644 --- a/playground/nuxt.config.ts +++ b/playground/nuxt.config.ts @@ -15,7 +15,7 @@ export default defineNuxtConfig({ defaultLocale: 'en_US', profileId: 'pfl_E5EmGZ98YT', testMode: true, - includeScriptGlobally: false, + includeScriptGlobally: true, }, devtools: { enabled: true }, }) From 6b40ab4e20f455d634ad842147d1433ea0a68e3a Mon Sep 17 00:00:00 2001 From: Niklas Wolf Date: Mon, 17 Jun 2024 16:09:34 +0200 Subject: [PATCH 5/8] fix error while building package --- src/module.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/module.ts b/src/module.ts index 63f1b77..0500d62 100644 --- a/src/module.ts +++ b/src/module.ts @@ -17,7 +17,7 @@ export default defineNuxtModule({ async setup(options, nuxt) { const resolver = createResolver(import.meta.url) nuxt.options.runtimeConfig.public.molliePaymentsComponents = defu( - nuxt.options.runtimeConfig.public.molliePaymentsComponents, + nuxt.options.runtimeConfig.public.molliePaymentsComponents as MollieOptions, options, ) From 61a637e34f9cb925a9835459b2b4bcb8a2805fdb Mon Sep 17 00:00:00 2001 From: Niklas Wolf Date: Fri, 21 Jun 2024 11:46:16 +0200 Subject: [PATCH 6/8] fix linter errors --- playground/pages/index.vue | 86 ++++++++++++++++++------------------ playground/pages/without.vue | 53 +++++++++++----------- 2 files changed, 72 insertions(+), 67 deletions(-) diff --git a/playground/pages/index.vue b/playground/pages/index.vue index 9fc4dbe..ba7b68a 100644 --- a/playground/pages/index.vue +++ b/playground/pages/index.vue @@ -6,54 +6,56 @@ const CreditCardToken = ref() const IdealError = ref() From 49e377f3621a8d7ab41e40ca07513eabd3e256d3 Mon Sep 17 00:00:00 2001 From: Niklas Wolf Date: Fri, 21 Jun 2024 11:51:02 +0200 Subject: [PATCH 7/8] feat: add navbar for different pages to playground --- playground/components/NavigationBar.vue | 15 +++++++++++++++ playground/pages/index.vue | 2 ++ playground/pages/without.vue | 3 +++ 3 files changed, 20 insertions(+) create mode 100644 playground/components/NavigationBar.vue diff --git a/playground/components/NavigationBar.vue b/playground/components/NavigationBar.vue new file mode 100644 index 0000000..15796a5 --- /dev/null +++ b/playground/components/NavigationBar.vue @@ -0,0 +1,15 @@ + + + + + diff --git a/playground/pages/index.vue b/playground/pages/index.vue index ba7b68a..450e612 100644 --- a/playground/pages/index.vue +++ b/playground/pages/index.vue @@ -1,5 +1,6 @@