From 3d41922100915a4acfbda36700c4e4838a212c8c Mon Sep 17 00:00:00 2001 From: Sebastian Benz Date: Fri, 10 Jan 2020 23:13:30 +0100 Subject: [PATCH] Support bindings on non-AMP elements Going with a wrapper based approach for none AMP elements for now: ```
``` I don't see a different way to support the `bindX` shortcut for non-AMP elements. --- .../pages/components/AmpBind.tsx | 20 ++++++--- .../test/out/components/AmpBind.out | 14 +++++-- lib/next-amp/README.md | 22 ++++++++-- lib/next-amp/build/generate.js | 5 +++ lib/next-amp/build/generateAmpBindProps.js | 15 +++++++ lib/next-amp/src/components/AmpBind.tsx | 41 +++++++++++++++++++ 6 files changed, 103 insertions(+), 14 deletions(-) create mode 100644 lib/next-amp/build/generateAmpBindProps.js create mode 100644 lib/next-amp/src/components/AmpBind.tsx diff --git a/lib/next-amp-demo/pages/components/AmpBind.tsx b/lib/next-amp-demo/pages/components/AmpBind.tsx index cb3eca983..1b0a89ec4 100644 --- a/lib/next-amp-demo/pages/components/AmpBind.tsx +++ b/lib/next-amp-demo/pages/components/AmpBind.tsx @@ -15,23 +15,31 @@ */ import React from 'react'; -import {AmpImg, AmpCarousel} from '@ampproject/toolbox-next-amp/src-gen'; +import {AmpBind, AmpImg, AmpCarousel} from '@ampproject/toolbox-next-amp'; export const config = {amp: true}; export default () => ( <>

amp-bind

+ +

Binding AMP Elements

- - - + + + + +

Binding non-AMP Elements

+ +
+ + ); diff --git a/lib/next-amp-demo/test/out/components/AmpBind.out b/lib/next-amp-demo/test/out/components/AmpBind.out index 293d0f905..9ba6c2fbe 100644 --- a/lib/next-amp-demo/test/out/components/AmpBind.out +++ b/lib/next-amp-demo/test/out/components/AmpBind.out @@ -11,13 +11,13 @@ @@ -25,6 +25,7 @@

amp-bind

+

Binding AMP Elements

Next slide +

Binding non-AMP Elements

+
+ diff --git a/lib/next-amp/README.md b/lib/next-amp/README.md index 2eec7af0b..fb5c92888 100644 --- a/lib/next-amp/README.md +++ b/lib/next-amp/README.md @@ -215,14 +215,28 @@ Better than Option 1, but makes things more complicated. ### amp-bind -For `amp-bind` expressions, the `amp-bind` extension is automatically imported using the same approach as for `amp-fx-collection`. The following will work out-of-the-box: +AMP bindings can be expressed using the the `bindX` short-form instead of `data-amp-bind-x`. A nice +side-effect is that most JS IDEs will offer component specific code-completion once you've +typed `bind`. + +Here is an example binding an `AmpImg`'s `src` attribute via `bindSrc`: + +``` + + +``` + +Unfortunately, this doesn't work for non-AMP tags such as `div` or `input`. In this case you need to +wrap the element inside the `AmpBind` component: ``` -
- ``` -State can be initialized via: +`amp-state` can be initialized via: ``` {{ diff --git a/lib/next-amp/build/generate.js b/lib/next-amp/build/generate.js index afe017fa1..527db6612 100644 --- a/lib/next-amp/build/generate.js +++ b/lib/next-amp/build/generate.js @@ -22,6 +22,7 @@ const loadAmp = require('./amp'); const generateTypes = require('./generateTypes.js'); const generateComponents = require('./generateComponents'); const generateVersionMapping = require('./generateVersionMapping'); +const generateBindProps = require('./generateAmpBindProps'); const prettierConfig = require('../../../prettier.config.js'); @@ -29,6 +30,7 @@ const config = { importHelper: 'AmpCustomElement.tsx', componentWrapper: 'index.tsx', versionMapping: 'versionMapping.ts', + bindProps: 'AmpBindProps.ts', typeDefinition: 'amp.d.tsx', dist: join(__dirname, '../src-gen/'), lincenseHeader: `/** @@ -63,6 +65,9 @@ async function build() { const versionMapping = generateVersionMapping(config, amp); writeFile(config.versionMapping, versionMapping); + + const bindProps = generateBindProps(config, amp); + writeFile(config.bindProps, bindProps); } function writeFile(name, content) { diff --git a/lib/next-amp/build/generateAmpBindProps.js b/lib/next-amp/build/generateAmpBindProps.js new file mode 100644 index 000000000..fc17027cc --- /dev/null +++ b/lib/next-amp/build/generateAmpBindProps.js @@ -0,0 +1,15 @@ +module.exports = (config, amp) => { + const bindAttrs = Array.from( + new Set(amp.tags.map(tag => tag.bindAttrs.map(a => `${a.name}?: string`)).flat()).values() + ); + return `${config.lincenseHeader} + + import * as React from 'react'; + + type AmpBindProps = { + children: React.ReactElement; + ${bindAttrs.join(';\n')} + }; + export default AmpBindProps; +`; +}; diff --git a/lib/next-amp/src/components/AmpBind.tsx b/lib/next-amp/src/components/AmpBind.tsx new file mode 100644 index 000000000..fe2519929 --- /dev/null +++ b/lib/next-amp/src/components/AmpBind.tsx @@ -0,0 +1,41 @@ +/** + * Copyright 2019 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as React from 'react'; +import {AmpIncludeCustomElement} from '../../src-gen/'; +import AmpBindProps from '../../src-gen/AmpBindProps'; + +/** + * Renders an amp-state element, by either adding local state via `value` + * or remote state via the `src` property. + */ +const AmpBind: React.FunctionComponent = ({children, ...rest}) => { + const newProps = {}; + Object.keys(rest).forEach(key => { + if (/^bind[A-Z]/g.test(key)) { + const newKey = `data-amp-bind-${key.substring(4).toLowerCase()}`; + newProps[newKey] = rest[key]; + } + }); + return ( + <> + + {React.cloneElement(children, newProps)} + + ); +}; + +export default AmpBind;