Skip to content

Commit

Permalink
feat!: custom amp styles (#206)
Browse files Browse the repository at this point in the history
* feat: custom amp styles

* docs: update options doc

* fix: load style

* chore: update eslint ignore

* fix: load custom file

* chore: refactor plugin
  • Loading branch information
farnabaz authored Dec 12, 2020
1 parent c39c47e commit 3950c70
Show file tree
Hide file tree
Showing 14 changed files with 1,667 additions and 2,279 deletions.
2 changes: 1 addition & 1 deletion .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ dist
coverage

# Plugin
lib/plugin.js
templates/plugin.js
1 change: 1 addition & 0 deletions docs/api/options.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ General options of amp module
| Option | Default | Valid values | Description |
| ------ | ------- | ------------ | ----------- |
| cdnBase | `https://cdn.ampproject.org/v0/` | Any String | A CDN Domain to load AMP elements scripts |
| css | `~/assets/style/amp-custom.css` | Path to CSS style | Custom styles for AMP pages |
| tags | `{}` | Object of tag options | Define new tags or modify current tags, for instance if you want to use `amp-mustache` version 0.1, tags value must be `{ 'amp-mustache': { version: '0.1' } }` |
| origin | `` | Any String | Main domain of website. Using this AMP modules tries to add missing canonical link for pages. |
| mode | `hybrid` | `only\|hybrid\|false` | Default behaviour of amp module. (`only` all pages serve in AMP mode by default, `hybrid` pages serves in both normal and AMP mode, `false` pages does not serve AMP by default ) |
Expand Down
5 changes: 5 additions & 0 deletions example/assets/styles/amp-custom.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
$bg-color: #0cf;

body {
background: $bg-color;
}
3 changes: 0 additions & 3 deletions example/assets/styles/default.amp.css

This file was deleted.

File renamed without changes.
13 changes: 10 additions & 3 deletions example/nuxt.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,15 @@ module.exports = {
render: {
resourceHints: false
},
css: [
'~/assets/styles/default.scss'
],
build: {
extractCSS: true
},
modules: [
[ 'nuxt-i18n', {
locales: [ 'en', 'fr' ],
['nuxt-i18n', {
locales: ['en', 'fr'],
defaultLocale: 'en',
vueI18n: {
fallbackLocale: 'en',
Expand All @@ -22,10 +28,11 @@ module.exports = {
}
}
}
} ],
}],
{ handler: require('../') }
],
amp: {
css: '~/assets/styles/amp-custom.scss',
origin: 'http://localhost:3000'
}
}
44 changes: 18 additions & 26 deletions lib/module.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
const { readdirSync } = require('fs')
const { resolve, join } = require('path')
const consola = require('consola')
const chalk = require('chalk')
const { getTags, getNecessaryScripts } = require('./tags')

const logger = consola.withScope('@nuxtjs/amp')
const { logger } = require('./utils')

const AMPBoilerplate = '<style amp-boilerplate>body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}</style><noscript><style amp-boilerplate>body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}</style></noscript>' +
'<script async src="https://cdn.ampproject.org/v0.js"></script>'
Expand All @@ -13,8 +10,10 @@ const scriptPattern = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi
const ampBodyPattern = /<amp-body[^>]*>([.\S\s]*)<\/amp-body>/

module.exports = function (moduleOptions) {
const { nuxt } = this
const options = {
cdnBase: undefined,
css: undefined,
origin: '',
mode: 'hybrid',
tags: {},
Expand All @@ -25,15 +24,18 @@ module.exports = function (moduleOptions) {
}

registerPlugin.call(this, options)
copyAMP.call(this, options)
registerRendererHook.call(this, options)
ensureMeta.call(this, options)

if (options.validator && this.nuxt.options.dev) {
if (options.validator && nuxt.options.dev) {
registerValidator.call(this, options)
}

processRoutes.call(this, options)

// alias amp runtime
const runtimeDir = resolve(__dirname, './runtime')
nuxt.options.alias['~amp'] = runtimeDir
}

function processRoutes (options) {
Expand Down Expand Up @@ -142,9 +144,14 @@ function registerRendererHook (options) {

params.HEAD = params.HEAD
.replace(scriptPattern, v => (v.includes('custom-element') || v.includes('application/ld+json')) ? v : '')
.replace(/<\/style>\S*<style [^>]*>/g, '')
.replace(/<style/, '<style amp-custom')
.replace('@charset "UTF-8";', '')
/**
* Remove external styles
*/
.replace(/<style[^>]*>.*?<\/style>/g, '')
/**
* Remove external stylesheet
*/
.replace(/<link[^>]*rel="stylesheet".*>/gi, '')

params.HEAD += AMPBoilerplate

Expand All @@ -154,27 +161,12 @@ function registerRendererHook (options) {

function registerPlugin (options) {
this.addPlugin({
src: resolve(__dirname, 'amp', 'plugin.js'),
fileName: join('amp', 'plugin.js'),
src: resolve(__dirname, '../templates', 'plugin.js'),
fileName: join('amp.js'),
options
})
}

function copyAMP (options) {
const coreRoot = resolve(__dirname, 'amp')

for (const file of readdirSync(coreRoot)) {
if (file === 'plugin.js') {
continue
}
this.addTemplate({
src: resolve(coreRoot, file),
fileName: join('amp', file),
options
})
}
}

function find (arr, key, val) {
return arr.find(obj => val ? obj[key] === val : obj[key])
}
Expand Down
5 changes: 1 addition & 4 deletions lib/amp/components.js → lib/runtime/components.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import Vue from 'vue'
// @vue/component
const AMPMustache = {
export const AMPMustache = {
name: 'AmpMustache',
render (h) {
return h('template', {
Expand All @@ -13,5 +12,3 @@ const AMPMustache = {
}, this.$slots.default)
}
}

Vue.component(AMPMustache.name, AMPMustache)
42 changes: 15 additions & 27 deletions lib/amp/plugin.js → lib/runtime/plugin.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@

import VueMeta from 'vue-meta'
import './components'

const [vueMetaMajor] = VueMeta.version.split('.')

Expand All @@ -18,7 +16,7 @@ function pick (...args) {
}
}

export default function (ctx, inject) {
export default function (ctx, { origin, mode }) {
const { route, req } = ctx
if (!route.matched[0]) {
return
Expand All @@ -30,7 +28,7 @@ export default function (ctx, inject) {
let ampMode = pick(
options.amp,
metaAMP,
'<%= options.mode %>'
mode
)

let isAMP = false
Expand Down Expand Up @@ -58,29 +56,23 @@ export default function (ctx, inject) {
break
}

const $request = req || {}
/**
* This will use to detect amp request on render hook
*/
$request.isAMP = isAMP
ctx.$isAMP = isAMP

inject('req', $request)
inject('isAMP', isAMP)
inject('ampMode', ampMode)

if (ampMode !== false && !options._amp) {
options.head = createCustomHead(options.head)
options.head = createCustomHead(options.head, origin)
options.layout = createCustomLayout(options.layout, options.ampLayout)
options._amp = true
}
return {
/**
* This will use to detect amp request on render hook
*/
req: req || { isAMP },
isAMP,
ampMode
}
}

const createCustomHead = originalHead => function customHead () {
let origin
if (process.server) {
origin = '<%= options.origin %>'
} else {
const createCustomHead = (originalHead, origin) => function customHead () {
if (!process.server) {
origin = window.location.origin
}

Expand All @@ -90,7 +82,7 @@ const createCustomHead = originalHead => function customHead () {
head = originalHead.call(this)
break
case 'object':
head = { ...originalHead } // TODO
head = { ...originalHead }
break
default:
head = {}
Expand Down Expand Up @@ -119,11 +111,7 @@ const createCustomHead = originalHead => function customHead () {

ensureKey(head, 'htmlAttrs', {})

if (vueMetaMajor >= 2) {
head.htmlAttrs.amp = true
} else {
head.htmlAttrs.amp = undefined
}
head.htmlAttrs.amp = vueMetaMajor >= 2 ? true : undefined

ensureKey(head, 'bodyAttrs', {})
ensureKey(head.bodyAttrs, 'class', '')
Expand Down
3 changes: 3 additions & 0 deletions lib/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import consola from 'consola'

export const logger = consola.withScope('@nuxtjs/amp')
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
"dependencies": {
"amphtml-validator": "^1.0.33",
"chalk": "^4.1.0",
"consola": "^2.15.0"
"consola": "^2.15.0",
"raw-loader": "^4.0.2"
},
"devDependencies": {
"@babel/core": "^7.12.10",
Expand All @@ -51,10 +52,12 @@
"husky": "^4.3.5",
"jest": "^26.6.3",
"jest-puppeteer": "^4.4.0",
"nuxt-edge": "^2.13.1-26548796.11a87d85",
"node-sass": "^5.0.0",
"nuxt-edge": "^2.14.8-26776834.e02fecdf",
"nuxt-i18n": "^6.16.0",
"puppeteer": "^5.5.0",
"request": "2.88.2",
"sass-loader": "^10.1.0",
"standard-version": "^9.0.0"
}
}
19 changes: 19 additions & 0 deletions templates/plugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import Vue from 'vue'
import ampPlugin from '~amp/plugin'
import { AMPMustache } from '~amp/components'

Vue.component(AMPMustache.name, AMPMustache)

export default async function (ctx, inject) {
const result = ampPlugin(ctx, {
origin: '<%= options.origin %>',
mode: '<%= options.mode %>'
})
if (result) {
Object.keys(result).forEach(key => inject(key, result[key]))
}
<% if (options.css) { %>if (ctx.$isAMP) {
const cssText = await import('!!raw-loader!sass-loader!<%= options.css %>').then(m => m.default || m)
ctx.app.head.style.push({ cssText, type: 'text/css', 'amp-custom': '' })
}<% } %>
}
47 changes: 24 additions & 23 deletions test/plugin.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const Plugin = require('../lib/amp/plugin')
const Plugin = require('../lib/runtime/plugin')

describe('Plugin', () => {
let route, ctx
Expand Down Expand Up @@ -26,61 +26,62 @@ describe('Plugin', () => {
})

it('Inject `isAMP` and `ampMode`', () => {
Plugin.default(ctx, inject)
expect(ctx.app).toHaveProperty('$isAMP')
expect(ctx.app).toHaveProperty('$ampMode')
const result = Plugin.default(ctx, {})
expect(result).toHaveProperty('isAMP')
expect(result).toHaveProperty('ampMode')
})

it('Do nothing if matched routes are empty', () => {
route.matched = []
Plugin.default(ctx, inject)
Plugin.default(ctx, {})
expect(ctx.app.$isAMP).toBeUndefined()
expect(ctx.app.$ampMode).toBeUndefined()
})

it('Detect non AMP page', () => {
Plugin.default(ctx, inject)
expect(ctx.app.$isAMP).toEqual(false)
expect(ctx.app.$ampMode).toEqual(false)
const result = Plugin.default(ctx, inject)
expect(result.isAMP).toEqual(false)
expect(result.ampMode).toEqual(false)
})

it('Detect non AMP from route meta (hybrid)', () => {
route.meta.amp = 'hybrid'
Plugin.default(ctx, inject)
expect(ctx.app.$isAMP).toEqual(false)
expect(ctx.app.$ampMode).toEqual('hybrid')
const result = Plugin.default(ctx, inject)
expect(result.isAMP).toEqual(false)
expect(result.ampMode).toEqual('hybrid')
})

it('Detect AMP from route meta (only)', () => {
route.meta.amp = 'only'
Plugin.default(ctx, inject)
expect(ctx.app.$isAMP).toEqual(true)
expect(ctx.app.$ampMode).toEqual('only')
const result = Plugin.default(ctx, inject)
expect(result.isAMP).toEqual(true)
expect(result.ampMode).toEqual('only')
})

it('Detect AMP from route meta (hybrid)', () => {
route.meta.amp = 'hybrid'
route.path = '/amp'
Plugin.default(ctx, inject)
expect(ctx.app.$isAMP).toEqual(true)
expect(ctx.app.$ampMode).toEqual('hybrid')
const result = Plugin.default(ctx, inject)
expect(result.isAMP).toEqual(true)
expect(result.ampMode).toEqual('hybrid')
})

it('Detect AMP from route component options', () => {
route.matched[0].components.default.options.amp = 'only'
Plugin.default(ctx, inject)
expect(ctx.app.$isAMP).toEqual(true)
expect(ctx.app.$ampMode).toEqual('only')
const result = Plugin.default(ctx, inject)
expect(result.isAMP).toEqual(true)
expect(result.ampMode).toEqual('only')
})

it('Detect non AMP from route component options', () => {
route.matched[0].components.default.options.amp = false
Plugin.default(ctx, inject)
expect(ctx.app.$isAMP).toEqual(false)
expect(ctx.app.$ampMode).toEqual(false)
const result = Plugin.default(ctx, inject)
expect(result.isAMP).toEqual(false)
expect(result.ampMode).toEqual(false)
})

it('Evaluate ampLayout option', () => {
ctx.app.$isAMP = true
route.matched[0].components.default.options.amp = 'only'
route.matched[0].components.default.options.ampLayout = function () {
return 'custom.amp.layout'
Expand Down
Loading

0 comments on commit 3950c70

Please # to comment.