nuxt-community / amp-module

AMP Module for Nuxt 2
https://codesandbox.io/s/github/nuxt-community/amp-module/
MIT License
204 stars 36 forks source link

$isAMP check is not working #71

Closed umeshgopoint closed 4 years ago

umeshgopoint commented 4 years ago

Hi, I have installed this module and while using this $isAMP check-in component section then its not working. Here is my code :

if (this.$isAMP) {
   console.log("AMP is working")
}else {
   console.log("AMP is Not working")
}

Full Code--

import _clamp from "lodash/clamp"
import _get from "lodash/get"
import Vue from "vue"
export default {
   amp: 'only',
   ampLayout: 'default',
    props: {
        object: {
            type: Object,
            default: () => ({})
        },
        html: String,
        src: String,
        height: [String, Number],
        width: [String, Number],
        aspect: [String, Number],
        videoSrc: [String, Boolean],
        size: {
            type: String,
            default: "full"
        },
        color: {
            type: String,
            default: ""
        },
        respectMax: {
            type: Boolean,
            default: false
        },
        fillSpace: {
            type: Boolean,
            default: false
        },
        fit: {
            type: String,
            default: "cover"
        },
        poster: {
            type: [String, Boolean],
            default: ""
        },
        volume: {
            type: Number,
            default: 0
        },
        loop: {
            type: Boolean,
            default: true
        },
        progressHandler: {
            type: Function,
            required: false,
            default: () => {}
        },
        useTransition: {
            type: Boolean,
            required: false,
            default: false
        }
    },
    data() {
        return {
            loading: true,
            imageWidth: 0,
            imageHeight: 0,
            inView: false,
            observerImgSettings: {
                callback: this.setImgSrc,
                intersection: {
                    rootMargin: "150px 0px 150px 0px",
                    threshold: 0
                }
            },
            observerVideoSettings: {
                callback: this.setVideoSrc,
                intersection: {
                    rootMargin: "150px 150px 150px 150px",
                    threshold: 0
                }
            }
        }
    },
    computed: {
        aspectPadding() {
            // default to defined aspect, or calculate
            const calculatedAspect =
                (this.parsedHeight / this.parsedWidth) * 100
            return this.aspect || calculatedAspect || 56.25
        },
        classes() {
            return [
                "rsp-image-module",
                "responsive-image",
                `fit-${this.fit}`,
                { loading: this.loading },
                { "fill-space": this.fillSpace },
                { "has-video": this.parsedVideoSrc },
                { "in-view": this.inView }
            ]
        },
        imageTag() {
            // TODO: Add other img attributes
             if (this.$isAMP) {
                console.log("AMP is working")

            }else {
                console.log("AMP is Not working")
            }

            const fallback = `<img data-umesh="Chandra" src="${this.parsedSrc}" alt="${this.parsedAlt}">`

            if (this.html) return this.html
            let html = fallback

            // Make src and srcset data attributes
            html = html.replace("src=", "data-src=")
            html = html.replace("srcset=", "data-srcset=")
            return html
        },
        innerHtml() {
            return this.imageTag || ""
        },
        isAcf() {
            // check to see if this is an ACF-serialized object
            // search for the existence of keys that ACF objects have but Rest-Easy ones don't
            return (
                this.object.hasOwnProperty("filesize") &&
                this.object.hasOwnProperty("mime_type") &&
                this.object.hasOwnProperty("modified")
            )
        },
        outerStyles() {
            const styles = {}
            // set max dims if needed
            if (this["respect-max"]) {
                styles["max-width"] = `${this.parsedWidth}px`
                styles["max-height"] = `${this.parsedHeight}px`
            }
            // add color bg if needed
            if (
                this.parsedColor &&
                this.parsedColor !== "transparent" &&
                this.loading
            ) {
                // set color or SVG background depending on settings
                if (this.fit == "cover")
                    styles["background-color"] = this.parsedColor
                else styles["background-image"] = `url("${this.svgBG}")`
                // set fit
                styles["background-size"] = this.fit
            }
            return styles
        },
        parsedAlt() {
            return _get(this, "object.alt", "")
        },
        parsedColor() {
            return (
                this.color ||
                _get(this.object, "primary_color") ||
                "transparent"
            )
        },
        parsedFocus() {
            return _get(this, "object.focus", { x: 50, y: 50 })
        },
        parsedHeight() {
            // default to defined height
            if (this.height) return parseInt(this.height)
            return this.imageHeight
        },
        parsedPoster() {
            if (this.poster === null) return ""
            return this.poster && this.poster.length
                ? this.poster
                : this.parsedSrc
        },
        parsedSrc() {
            // return hardcoded source if we have one
            if (this.src) return this.src
            // return ACF source if we have one (only respects fullscreen)
            if (this.isAcf) {
                return _get(this, "object.sizes.fullscreen", "")
            }
            return _get(this.targetSize, `url`)
        },
        parsedVideoSrc() {
            const metaString =
                _get(this.object, "meta.custom_video_url") ||
                _get(this.object, "alt", "")
            if (this.videoSrc || this.videoSrc === null) return this.videoSrc
            else return String(metaString).includes(".mp4") ? metaString : ""
        },
        parsedWidth() {
            // default to defined width
            if (this.width) return parseInt(this.width)
            return this.imageWidth
        },
        sizerStyles() {
            if (!this.fillSpace) {
                return {
                    paddingBottom: `${this.aspectPadding}%`
                }
            }
            return {}
        },
        svgBG() {
            if (!this.parsedColor || this.parsedColor == "transparent")
                return ""
            return `data:image/svg+xml;utf8,
                    <svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'
                        x='0px' y='0px' viewBox='0 0 ${this.imageWidth} ${this.imageHeight}' xml:space='preserve'>
                        <rect fill='${this.parsedColor}' width='${this.imageWidth}' height='${this.imageHeight}' />
                    </svg>`.replace(/\r?\n|\r/g, "")
        },
        targetSize() {
            // should return an object with { height, html, url, width }
            // return ACF sizes
            if (this.isAcf) {
                return {
                    width: _get(this, "object.sizes.fullscreen-width", 0),
                    height: _get(this, "object.sizes.fullscreen-height", 0),
                    url: _get(this, "object.sizes.fullscreen", "")
                }
            }
            // get sizes from image object
            const sizes = _get(this.object, `sizes`, {})
            // get specified size, or first available size
            return (
                _get(sizes, this.size) || sizes[_get(Object.keys(sizes), "[0]")]
            )
        },
        transitionKey() {
            if (this.useTransition) return this.parsedVideoSrc
            else return false
        }
    },
    watch: {
        object() {
            this.setObjectDimensions()
        },
        innerHtml() {
            this.setMediaClass()
            this.setFocalPoint()
        },
        volume() {
            this.setVolume()
        }
    },
    async mounted() {
        // ignore if our src is undefined
        if (!this.parsedSrc) return
        // const img = new Image()
        // img.src = this.parsedSrc
        // // image was already in cache,
        // // set loading var immediately
        // if (img.complete) this.loading = false
        // // set up height/width if we have an object

        this.setObjectDimensions()
        // wait for image to load...

        this.setMediaClass()
        this.setVolume()
        // make sure the wrapped image is rendered
        await Vue.nextTick()
        // set focal point
        this.setFocalPoint()

        // Check if there's a video progress handler Function
        // If there is pass it the current video details every 100ms
        // If there is but there's no video, pass it false once
        const video = this.$refs.video
        if (this.progressHandler) {
            if (video)
                this.progressInterval = setInterval(() => {
                    this.progressHandler({
                        currentTime: video.currentTime,
                        duration: video.duration,
                        percentComplete: video.currentTime / video.duration
                    })
                }, 100)
            else this.progressHandler(false)
        }
    },
    destroyed() {
        if (this.progressInterval) clearInterval(this.progressInterval)
    },
    methods: {
        setImgSrc(isVisible, entry) {
            // Add in view class once
            if (isVisible) {
                this.inView = isVisible
            }

            if (this.$refs.imageWrap && isVisible && this.loading) {
                let lazyImage = this.$refs.imageWrap.querySelector("img")
                if (lazyImage.dataset.srcset) {
                    lazyImage.srcset = lazyImage.dataset.srcset
                }
                if (lazyImage.dataset.src) {
                    lazyImage.src = lazyImage.dataset.src
                }
                this.loading = false
            }
        },
        setVideoSrc(isVisible, entry) {
            // Add in view class once
            if (isVisible) {
                this.inView = isVisible
            }

            if (this.$refs.video && isVisible && this.loading) {
                const lazyVideo = this.$refs.video

                if (lazyVideo.dataset.poster) {
                    lazyVideo.poster = lazyVideo.dataset.poster
                }
                if (lazyVideo.dataset.src) {
                    lazyVideo.src = lazyVideo.dataset.src
                }
                this.loading = false
            }
        },
        setFocalPoint() {
            // find the wrapped image
            if (
                this.$refs.imageWrap &&
                this.$refs.imageWrap.querySelector("*")
            ) {
                const wrapped = this.$refs.imageWrap.querySelector("*")
                // set its position (default: 50% 50%)
                wrapped.style.objectPosition = `${this.parsedFocus.x}% ${this.parsedFocus.y}%`
            }
        },
        setMediaClass() {
            // give the "media" class to whatever we are rendering (img or video)
            if (!this.$el || !this.$el.querySelector) return
            Vue.nextTick(() => {
                const media = this.$el.querySelector(".image-sizer > *")
                if (media) media.classList.add("media")
            })
        },
        setObjectDimensions() {
            if (this.object) {
                this.imageWidth = _get(this.targetSize, `width`)
                this.imageHeight = _get(this.targetSize, `height`)
            }
        },
        async setVolume() {
            await Vue.nextTick()
            const video = this.$el.querySelector("video")
            if (video) {
                video.volume = _clamp(this.volume, 0, 1)
            }
        }
    }
}
farnabaz commented 4 years ago

@umeshgopoint Code is fine, Make sure you've added @nuxtjs/amp as a module in nuxt.config.js

umeshgopoint commented 4 years ago

@umeshgopoint Code is fine, Make sure you've added @nuxtjs/amp as a module in nuxt.config.js

Hi @farnabaz , I have integrated @nuxtjs/amp as a module in nuxt.config.js as given instruction. Here is my nuxt.config.js file settings. Please have a look.

import pkg from "./package"
require( "dotenv" ).config()
const axios = require( "axios" )
import redirects from "./assets/redirection-prohbtd-com-september-17-2019.json"

    export default {
        mode: "universal",
        env: {
            API_URL: process.env.API_URL || "https://locahost:3000"
        },

        /*
         ** Headers of the page
         */
        head: {
            meta: [
                { charset: "utf-8" },
                {
                    name: "viewport",
                    content: "width=device-width, initial-scale=1"
                }
            ],
            link: [ { rel: "icon", type: "image/x-icon", href: "/favicon.png" } ]
        },

        /*
         ** Customize the progress-bar color
         ** See: https://nuxtjs.org/api/configuration-loading#use-a-custom-loading-component
         */
        loading: {
            color: "#bfa87c",
            height: "4px"
        },

        sitemap: {
            routes() {
                return axios
                    .get( `${process.env.API_URL}/wp-json/phbtd/routes` )
                    .then( res => {
                        return res.data
                    } )
            }
        },

        /*
         ** Global CSS
         */
        css: [
            "~/styles/base.scss",
            "~/styles/transitions.scss",
            "~/styles/fonts.css"
        ],

        /*
         ** Plugins to load before mounting the App
         */
        plugins: [
            { src: "~/plugins/global-component-loader.js" },
            { src: "~/plugins/global-directive-loader.js" },
            { src: "~/plugins/global-svg-loader.js" },
            { src: "~/plugins/wp-shopify.js", mode: "client" },
            { src: "~/plugins/web-font-loader.client.js", mode: "client" },
            { src: "~/plugins/google-gtag.client.js", mode: "client" },
            { src: "~/plugins/jivo-chat.client.js", mode: "client" }
        ],

        /*
         ** Configure polyfills
         ** See: https://github.com/Timkor/nuxt-polyfill
         */
        polyfill: {
            features: [
                {
                    require: "intersection-observer",
                    detect: () => "IntersectionObserver" in window
                }
            ]
        },

        /*
         ** Nuxt.js modules
         */
        modules: [
            "@nuxtjs/axios",
            "@nuxtjs/style-resources",
            "nuxt-basic-auth-module",
            "nuxt-polyfill",
            "@nuxtjs/device",
            "@nuxtjs/redirect-module",
            "@nuxtjs/sitemap",
            "@nuxtjs/dotenv",
            [
                "nuxt-vuex-localstorage",
                {
                    localStorage: [ "shopify", "persisted" ],
                    mode: "debug"
                }
            ],
            '@nuxtjs/amp'
        ],

        /*
         ** Define redirects here
         */
        redirect: {
            rules: () => {
                let allRules = redirects.redirects.map( obj => {
                    // Get the :to url in a safe way
                    let to = "/"
                    if ( obj.action_data.url_from ) {
                        to = obj.action_data.url_from.replace(
                            "https://locahost:3000",
                            "/"
                            )
                    }

                    // Return a rule object formatted for @nuxtjs/redirect-module
                    // Uses regex
                    return {
                        from: `^${obj.url}$`,
                        to: to,
                        statusCode: 302 //obj.action_code
                    }
                } )

                // Add WordPress admin redirect
                allRules.push( {
                    from: "^/wp-admin",
                    to: `${process.env.API_URL}/wp-admin`,
                    statusCode: 302
                } )

                return allRules
            }
        },

        /*
         ** Load scss globally via styleResources
         */
        styleResources: {
            scss: [ "~/styles/vars.scss" ]
        },

        /*
         ** Axios options. Used for API queries
         ** See: https://axios.nuxtjs.org/
         */
        axios: {
            baseURL: process.env.API_URL
        },

        /*
         ** Customize router
         */
        router: {
            linkExactActiveClass: "exact-active-link",
            linkActiveClass: "active-link"
        },

        /*
         ** Build configuration
         */
        build: {
            // This and the transpile code below fix an issue with the spread operator in Safari 10.
            babel: {
                plugins: [ "transform-object-rest-spread" ]
            },
            transpile: [ "ky" ],
            extend( config, ctx ) {
                // This is used by plugins/global-svg-loader.js
                config.module.noParse = /\/assets\/svgs\/.+(svg$)/

                // Includes the Compiler version of Vue.
                // If you don't use the <wp-content> component, then you can delete this safely.
                config.resolve.alias["vue$"] = "vue/dist/vue.esm.js"

                // This stops a @nuxtjs/dotenv error.
                config.node = { fs: "empty" }
            }
        },

        /*
         ** Basic (htaccess) authentication configuration
         */
        basic: {
            name: "funkhaus",
            pass: "funkhaus",
            enabled: process.env.BASIC_AUTH_ENABLED === "true"
        },

        /*
         ** Server middleware modules
         */
        serverMiddleware: [
            "redirect-ssl" // Force SSL on all URLs
        ]
    }

Actaull, I want to know how to call the AMP inside the component form the page, Here is my component vue code

import _clamp from "lodash/clamp"
import _get from "lodash/get"
import Vue from "vue"
export default {
   amp: 'only',
   ampLayout: 'default',
    props: {
        object: {
            type: Object,
            default: () => ({})
        },
        html: String,
        src: String,
        height: [String, Number],
        width: [String, Number],
        aspect: [String, Number],
        videoSrc: [String, Boolean],
        size: {
            type: String,
            default: "full"
        },
        color: {
            type: String,
            default: ""
        },
        respectMax: {
            type: Boolean,
            default: false
        },
        fillSpace: {
            type: Boolean,
            default: false
        },
        fit: {
            type: String,
            default: "cover"
        },
        poster: {
            type: [String, Boolean],
            default: ""
        },
        volume: {
            type: Number,
            default: 0
        },
        loop: {
            type: Boolean,
            default: true
        },
        progressHandler: {
            type: Function,
            required: false,
            default: () => {}
        },
        useTransition: {
            type: Boolean,
            required: false,
            default: false
        }
    },
    data() {
        return {
            loading: true,
            imageWidth: 0,
            imageHeight: 0,
            inView: false,
            observerImgSettings: {
                callback: this.setImgSrc,
                intersection: {
                    rootMargin: "150px 0px 150px 0px",
                    threshold: 0
                }
            },
            observerVideoSettings: {
                callback: this.setVideoSrc,
                intersection: {
                    rootMargin: "150px 150px 150px 150px",
                    threshold: 0
                }
            }
        }
    },
    computed: {
        aspectPadding() {
            // default to defined aspect, or calculate
            const calculatedAspect =
                (this.parsedHeight / this.parsedWidth) * 100
            return this.aspect || calculatedAspect || 56.25
        },
        classes() {
            return [
                "rsp-image-module",
                "responsive-image",
                `fit-${this.fit}`,
                { loading: this.loading },
                { "fill-space": this.fillSpace },
                { "has-video": this.parsedVideoSrc },
                { "in-view": this.inView }
            ]
        },
        imageTag() {
            // TODO: Add other img attributes
             if (this.$isAMP) {
                console.log("AMP is working")

            }else {
                console.log("AMP is Not working")
            }

            const fallback = `<img data-umesh="Chandra" src="${this.parsedSrc}" alt="${this.parsedAlt}">`

            if (this.html) return this.html
            let html = fallback

            // Make src and srcset data attributes
            html = html.replace("src=", "data-src=")
            html = html.replace("srcset=", "data-srcset=")
            return html
        },
        innerHtml() {
            return this.imageTag || ""
        },
        isAcf() {
            // check to see if this is an ACF-serialized object
            // search for the existence of keys that ACF objects have but Rest-Easy ones don't
            return (
                this.object.hasOwnProperty("filesize") &&
                this.object.hasOwnProperty("mime_type") &&
                this.object.hasOwnProperty("modified")
            )
        },
        outerStyles() {
            const styles = {}
            // set max dims if needed
            if (this["respect-max"]) {
                styles["max-width"] = `${this.parsedWidth}px`
                styles["max-height"] = `${this.parsedHeight}px`
            }
            // add color bg if needed
            if (
                this.parsedColor &&
                this.parsedColor !== "transparent" &&
                this.loading
            ) {
                // set color or SVG background depending on settings
                if (this.fit == "cover")
                    styles["background-color"] = this.parsedColor
                else styles["background-image"] = `url("${this.svgBG}")`
                // set fit
                styles["background-size"] = this.fit
            }
            return styles
        },
        parsedAlt() {
            return _get(this, "object.alt", "")
        },
        parsedColor() {
            return (
                this.color ||
                _get(this.object, "primary_color") ||
                "transparent"
            )
        },
        parsedFocus() {
            return _get(this, "object.focus", { x: 50, y: 50 })
        },
        parsedHeight() {
            // default to defined height
            if (this.height) return parseInt(this.height)
            return this.imageHeight
        },
        parsedPoster() {
            if (this.poster === null) return ""
            return this.poster && this.poster.length
                ? this.poster
                : this.parsedSrc
        },
        parsedSrc() {
            // return hardcoded source if we have one
            if (this.src) return this.src
            // return ACF source if we have one (only respects fullscreen)
            if (this.isAcf) {
                return _get(this, "object.sizes.fullscreen", "")
            }
            return _get(this.targetSize, `url`)
        },
        parsedVideoSrc() {
            const metaString =
                _get(this.object, "meta.custom_video_url") ||
                _get(this.object, "alt", "")
            if (this.videoSrc || this.videoSrc === null) return this.videoSrc
            else return String(metaString).includes(".mp4") ? metaString : ""
        },
        parsedWidth() {
            // default to defined width
            if (this.width) return parseInt(this.width)
            return this.imageWidth
        },
        sizerStyles() {
            if (!this.fillSpace) {
                return {
                    paddingBottom: `${this.aspectPadding}%`
                }
            }
            return {}
        },
        svgBG() {
            if (!this.parsedColor || this.parsedColor == "transparent")
                return ""
            return `data:image/svg+xml;utf8,
                    <svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'
                        x='0px' y='0px' viewBox='0 0 ${this.imageWidth} ${this.imageHeight}' xml:space='preserve'>
                        <rect fill='${this.parsedColor}' width='${this.imageWidth}' height='${this.imageHeight}' />
                    </svg>`.replace(/\r?\n|\r/g, "")
        },
        targetSize() {
            // should return an object with { height, html, url, width }
            // return ACF sizes
            if (this.isAcf) {
                return {
                    width: _get(this, "object.sizes.fullscreen-width", 0),
                    height: _get(this, "object.sizes.fullscreen-height", 0),
                    url: _get(this, "object.sizes.fullscreen", "")
                }
            }
            // get sizes from image object
            const sizes = _get(this.object, `sizes`, {})
            // get specified size, or first available size
            return (
                _get(sizes, this.size) || sizes[_get(Object.keys(sizes), "[0]")]
            )
        },
        transitionKey() {
            if (this.useTransition) return this.parsedVideoSrc
            else return false
        }
    },
    watch: {
        object() {
            this.setObjectDimensions()
        },
        innerHtml() {
            this.setMediaClass()
            this.setFocalPoint()
        },
        volume() {
            this.setVolume()
        }
    },
    async mounted() {
        // ignore if our src is undefined
        if (!this.parsedSrc) return
        // const img = new Image()
        // img.src = this.parsedSrc
        // // image was already in cache,
        // // set loading var immediately
        // if (img.complete) this.loading = false
        // // set up height/width if we have an object

        this.setObjectDimensions()
        // wait for image to load...

        this.setMediaClass()
        this.setVolume()
        // make sure the wrapped image is rendered
        await Vue.nextTick()
        // set focal point
        this.setFocalPoint()

        // Check if there's a video progress handler Function
        // If there is pass it the current video details every 100ms
        // If there is but there's no video, pass it false once
        const video = this.$refs.video
        if (this.progressHandler) {
            if (video)
                this.progressInterval = setInterval(() => {
                    this.progressHandler({
                        currentTime: video.currentTime,
                        duration: video.duration,
                        percentComplete: video.currentTime / video.duration
                    })
                }, 100)
            else this.progressHandler(false)
        }
    },
    destroyed() {
        if (this.progressInterval) clearInterval(this.progressInterval)
    },
    methods: {
        setImgSrc(isVisible, entry) {
            // Add in view class once
            if (isVisible) {
                this.inView = isVisible
            }

            if (this.$refs.imageWrap && isVisible && this.loading) {
                let lazyImage = this.$refs.imageWrap.querySelector("img")
                if (lazyImage.dataset.srcset) {
                    lazyImage.srcset = lazyImage.dataset.srcset
                }
                if (lazyImage.dataset.src) {
                    lazyImage.src = lazyImage.dataset.src
                }
                this.loading = false
            }
        },
        setVideoSrc(isVisible, entry) {
            // Add in view class once
            if (isVisible) {
                this.inView = isVisible
            }

            if (this.$refs.video && isVisible && this.loading) {
                const lazyVideo = this.$refs.video

                if (lazyVideo.dataset.poster) {
                    lazyVideo.poster = lazyVideo.dataset.poster
                }
                if (lazyVideo.dataset.src) {
                    lazyVideo.src = lazyVideo.dataset.src
                }
                this.loading = false
            }
        },
        setFocalPoint() {
            // find the wrapped image
            if (
                this.$refs.imageWrap &&
                this.$refs.imageWrap.querySelector("*")
            ) {
                const wrapped = this.$refs.imageWrap.querySelector("*")
                // set its position (default: 50% 50%)
                wrapped.style.objectPosition = `${this.parsedFocus.x}% ${this.parsedFocus.y}%`
            }
        },
        setMediaClass() {
            // give the "media" class to whatever we are rendering (img or video)
            if (!this.$el || !this.$el.querySelector) return
            Vue.nextTick(() => {
                const media = this.$el.querySelector(".image-sizer > *")
                if (media) media.classList.add("media")
            })
        },
        setObjectDimensions() {
            if (this.object) {
                this.imageWidth = _get(this.targetSize, `width`)
                this.imageHeight = _get(this.targetSize, `height`)
            }
        },
        async setVolume() {
            await Vue.nextTick()
            const video = this.$el.querySelector("video")
            if (video) {
                video.volume = _clamp(this.volume, 0, 1)
            }
        }
    }
}

Please check and let me know can we call AMP directly in Component Vue.

Awaiting for your kind response. Actually I am a beginner in NUXT.

farnabaz commented 4 years ago

Is this content of your page? or it's just a standalone component outside of the pages directory?

amp: 'only' and ampLayout: 'default' are only valid in pages (files inside pages directory)