Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
sergei-sss committed Sep 21, 2021
1 parent ef5b864 commit d25be61
Show file tree
Hide file tree
Showing 22 changed files with 769 additions and 1 deletion.
10 changes: 10 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const config = {
parser: 'babel-eslint',
extends: ['@magento'],
rules: {
'no-undef': 'off',
'no-useless-escape': 'off'
}
};

module.exports = config;
15 changes: 15 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
.idea
.vscode
coverage
node_modules
storybook-dist
test-results
dist
.DS_Store
.env
build-stats.json
npm-debug.log
lastCachedGraphQLSchema.json
test-report.xml
test-results.json
yarn-error.log
83 changes: 82 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,82 @@
# mageworx-seo-veniapwa
# MageWorx SEO Suite Ultimate extension for Magento Venia PWA
This add-on integrates [SEO Suite Ultimate extension for Magento 2](https://www.mageworx.com/magento2-extensions/seo-tools-services.html) using [MageWorx SeoBase GraphQl extension](https://github.com/mageworx/MageWorx_SeoBaseGraphQl) with [Magento 2 Venia PWA storefront](https://magento.github.io/pwa-studio/venia-pwa-concept/).

## Features
- Canonical URLs to eliminate duplicate content
- Alternate URLs
- Meta robots
- Rich snippets
- Seller & Website markup

## Upload the extension
1. Create directory `@mageworx/seo-veniapwa` in the root of your project
2. Copy this project to `@mageworx/seo-veniapwa`
3. Run `yarn add file:./@mageworx/seo-veniapwa` in the root of your project
4. Open `local-intercept.js` in the root of your project and put this code into `function localIntercept`. Pay attention, `function localIntercept` must have `targets` as parameter (you can see example of `local-intercept.js` in `@mageworx/seo-veniapwa/documentation`).
```
/* MageWorx seo-veniapwa veniapwa start */
const seoTargetables = Targetables.using(targets);
// product
const ProductDetails_seo = seoTargetables.reactComponent(
'@magento/venia-ui/lib/components/ProductFullDetail/productFullDetail.js'
);
const SeoProductDetails = ProductDetails_seo.addImport("{Seo} from '../../../../../../@mageworx/seo-veniapwa/src/components/Seo'");
ProductDetails_seo.insertAfterJSX(
'<section className={classes.description} />',
`<${SeoProductDetails} seoData={productDetails.seoAttributes}/>`
);
// category
const CategoryContent_seo = seoTargetables.reactComponent(
'@magento/venia-ui/lib/RootComponents/Category/categoryContent.js'
);
const SeoCategoryContent = CategoryContent_seo.addImport("{Seo} from '../../../../../../@mageworx/seo-veniapwa/src/components/Seo'");
CategoryContent_seo.insertAfterJSX(
'<article className={classes.root} />',
`<${SeoCategoryContent} seoData={talonProps.seoAttributes} />`
);
// cms page
const CmsPage_seo = seoTargetables.reactComponent(
'@magento/venia-ui/lib/RootComponents/CMS/cms.js'
);
const SeoCmsPage = CmsPage_seo.addImport("{Seo} from '../../../../../../@mageworx/seo-veniapwa/src/components/Seo'");
CmsPage_seo.surroundJSX(
'<CategoryList />',
`<div>`
);
CmsPage_seo.insertAfterJSX(
'<CategoryList />',
`<${SeoCmsPage} seoData={talonProps.seoAttributes} />`
);
/* MageWorx seo-veniapwa veniapwa end */
```
5. Check that your `local-intercept` has this code before `module.exports`, if don't have you should add them (you can see example of `local-intercept.js` in `@mageworx/seo-veniapwa/documentation`)
```
const { Targetables } = require('@magento/pwa-buildpack');
```
6. Let's run your project
```
yarn watch
```

## Urls config
You can change or add your custom urls for hreflang in `@mageworx/seo-veniapwa/src/hreflangs.config.js`, for exmaple:
```
const hreflangs_config = {
...
{type: "store", code: "default", url: "https://magento-store.com/"},
{type: "store", code: "toys", url: "https://magento-toys.com/"},
{type: "lang", code: "de-DE", url: "https://magento-store.com/de/"},
{type: "lang", code: "en-US", url: "https://magento-store.com/en/"},
...
};
```
On frontend it will be look like:
```
<link rel="alternate" hreflang="de-DE" href="https://magento-store.com/de/gear/bags.html" data-rh="true">
<link rel="canonical" href="https://magento-store.com/gear/bags.html" data-rh="true">
```


10 changes: 10 additions & 0 deletions babel.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"presets": [
"@babel/preset-react",
"@babel/preset-env"
],
"plugins": [
"@babel/plugin-transform-react-jsx",
"@babel/plugin-proposal-class-properties"
]
}
51 changes: 51 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
{
"name": "@mageworx/seo-veniapwa",
"author": "MageWorx",
"version": "1.0.0",
"main": "src/index.js",
"pwa-studio": {
"targets": {
"intercept": "src/intercept.js"
}
},
"scripts": {
"format": "prettier --ignore-path .gitignore \"src/**/*.+(ts|js|tsx)\" --write",
"lint": "eslint --ignore-path .gitignore 'src/**/{*.js,package.json}'",
"prepare": "install-peers"
},
"lint-staged": {
"./src/**/*.{ts,js,jsx,tsx}": [
"yarn lint --fix",
"yarn format"
]
},
"peerDependencies": {
"@apollo/client": "~3.1.2",
"@magento/peregrine": "~7.0.0",
"@magento/pwa-buildpack": ">=6.0.0",
"@magento/venia-ui": "~4.0.0",
"react": "~16.9.0",
"react-dom": "^16.12.0",
"graphql-tag": "~2.10.1",
"webpack": "~4.38.0"
},
"devDependencies": {
"@babel/core": "^7.11.6",
"@babel/plugin-proposal-class-properties": "^7.12.1",
"@babel/plugin-syntax-class-properties": "^7.12.1",
"babel-eslint": "~10.0.1",
"babel-preset-env": "^1.7.0",
"babel-preset-react": "^6.24.1",
"eslint": "^7.32.0",
"eslint-config-prettier": "^6.15.0",
"eslint-plugin-jsx-a11y": "^6.0.3",
"eslint-plugin-package-json": "^0.1.4",
"eslint-plugin-react": "^7.9.1",
"eslint-plugin-react-hooks": "^1.6.0",
"identity-obj-proxy": "^3.0.0",
"install-peers-cli": "^2.2.0",
"lint-staged": "^10.0.8",
"prettier": "^1.9.2",
"prettier-check": "^2.0.0"
}
}
7 changes: 7 additions & 0 deletions prettier.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const config = {
singleQuote: true,
tabWidth: 4,
trailingComma: 'none'
};

module.exports = config;
34 changes: 34 additions & 0 deletions src/components/Seo/Canonical.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React from 'react';
import { Helmet } from 'react-helmet-async';

import {findCodeFromConfig, getCurrentHostname} from "./features";

const Canonical = props => {
const {canonical} = props;
if (!canonical) return null;

let canonical_result
if (canonical.url) {
if (canonical.url.match(/https?:\/\//i)) {
canonical_result = <link rel="canonical" href={canonical.url} />;
}
else if (canonical.code) {
let elem_from_config = findCodeFromConfig(canonical.code, "store", true);
if (elem_from_config) {
canonical_result = <link rel="canonical" href={elem_from_config + canonical.url} />;
}
else return null;
}
else {
canonical_result = <link rel="canonical" href={getCurrentHostname(true) + canonical.url} />;
}
return (
<Helmet>
{canonical_result}
</Helmet>
);
}
return null;
};

export default Canonical;
26 changes: 26 additions & 0 deletions src/components/Seo/Hreflangs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React from 'react';
import { Helmet } from 'react-helmet-async';

import {findCodeFromConfig} from "./features";

const Hreflangs = props => {
const {mw_hreflangs} = props;
if (!mw_hreflangs || !mw_hreflangs.items) return null;

let hreflangs_result = mw_hreflangs.items.map((item, itemNum) => {
let elem_from_config = findCodeFromConfig(item.code, "lang",true);
if (elem_from_config) {
let url = elem_from_config + item.url;
return <link key={item.code + itemNum} rel="alternate" hreflang={item.code} href={url} />;
}
else return null;
})
return (
<Helmet>
{hreflangs_result}
</Helmet>
);
return null;
};

export default Hreflangs;
100 changes: 100 additions & 0 deletions src/components/Seo/Markup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import React from 'react';
import { Helmet } from 'react-helmet-async';

import {getCurrentHostname} from "./features";

const changeAllUrlsToLocal = (str) => {
str = str.replaceAll(/\\\//g, "/");
let resultUrl = str;

let currentHost = getCurrentHostname();
// for meta content
resultUrl = resultUrl.replaceAll(/^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)$/g, `${currentHost}$2"`);
// for script content (only url and image) (:?"image":")]
resultUrl = resultUrl.replaceAll(/(:?"url":")https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)"/g, `$1${currentHost}$3"`);
resultUrl = resultUrl.replaceAll(/(:?"image":")https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)"/g, `$1${currentHost}$3"`);
return resultUrl;
}

const getMetaArrayFromStr = (str) => {
return str.match(/<meta.+?\/>/gi);
}

const getScriptsArrayFromStr = (str) => {
return str.match(/<script.+?>.+?<\/script>/gi);
}

const getContentAndAttributesOfMeta = (str) => {
const metaData = {
content: "",
property: "",
}
let content = str.match(/<meta.+?content=\\?"(.+?)\\?".*\/>/i);
let property = str.match(/<meta.+?property=\\?"(.+?)\\?".*\/>/i);
if (content && content.length>1) {
metaData.content = changeAllUrlsToLocal(content[1]);
}
if (property && property.length>1) {
metaData.property = property[1];
}
return metaData;
}

const getContentAndAttributesOfScript = (str) => {
const scriptData = {
content: "",
type: "",
}
let content = str.match(/<script.+?>(.+?)<\/script>/i);
let type = str.match(/<script.+?type\=\\?"(.+?)\\?".*?>.+?<\/script>/i);
if (content && content.length>1) {
scriptData.content = changeAllUrlsToLocal(content[1]);
}
if (type && type.length>1) {
scriptData.type = type[1];
}
return scriptData;
}

const getMetaResultOfJsxFromStr = (str) => {
let metaArray = getMetaArrayFromStr(str);
let result = metaArray.map((script, num) => {
let attributes = getContentAndAttributesOfMeta(script);
const {content, property} = attributes;
return <meta key={num} property={property} content={content} />
});
return result;
}

const getScriptsResultOfJsxFromStr = (str) => {
let scriptsArray = getScriptsArrayFromStr(str);
let result = scriptsArray.map((script, num) => {
let attributes = getContentAndAttributesOfScript(script);
const {type, content} = attributes;
return <script key={num} type={type}>{content}</script>
});
return result;
}

const Markup = props => {
const {markup} = props;
if (!markup) return null;

let markup_result = [];
if (markup.rich_snippets) {
if (markup.rich_snippets.product) markup_result = markup_result.concat(getScriptsResultOfJsxFromStr(markup.rich_snippets.product));
if (markup.rich_snippets.seller) markup_result = markup_result.concat(getScriptsResultOfJsxFromStr(markup.rich_snippets.seller));
if (markup.rich_snippets.website) markup_result = markup_result.concat(getScriptsResultOfJsxFromStr(markup.rich_snippets.website));
if (markup.rich_snippets.webpage) markup_result = markup_result.concat(getScriptsResultOfJsxFromStr(markup.rich_snippets.webpage));
}
if (markup.social_markup) {
markup_result = markup_result.concat(getMetaResultOfJsxFromStr(markup.social_markup));
}
return (
<Helmet>
{markup_result}
</Helmet>
)
};

export default Markup;
41 changes: 41 additions & 0 deletions src/components/Seo/Seo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React from 'react';
import { Helmet } from 'react-helmet-async';

import Hreflangs from "./Hreflangs";
import Markup from "./Markup";
import Canonical from "./Canonical";

const Seo = props => {
const {seoData} = props;
if (!seoData) return null;

const {
meta_robots,
mw_canonical_url,
mw_hreflangs,
mw_seo_markup
} = seoData;

let canonical;
let meta_robots_JSX;
let hreflangs;
let markup;
if (seoData) {
meta_robots_JSX = meta_robots && <meta name="robots" content={meta_robots} />;
canonical = <Canonical canonical={mw_canonical_url} />
hreflangs = <Hreflangs mw_hreflangs={mw_hreflangs} />;
markup = <Markup markup={mw_seo_markup} />
}

return (
<>
<Helmet>
{meta_robots_JSX}
</Helmet>
{hreflangs}
{markup}
{canonical}
</>
)
};
export default Seo;
Loading

0 comments on commit d25be61

Please sign in to comment.