├── docs └── images │ ├── azure-static-endpoint.png │ ├── azure-static-website.png │ ├── netlify-build-settings.png │ └── netlify-env-vars.png ├── netlify.toml ├── readme.md ├── sitecore ├── Uniform SitecoreJSS Demo-items.0.0.1.zip ├── config │ ├── uniform-jss.config │ └── uniform-jss.unicorn.config └── serialization │ ├── content │ ├── uniform-jss.yml │ └── uniform-jss │ │ ├── Home.yml │ │ ├── Home │ │ ├── About.yml │ │ └── About │ │ │ ├── local.yml │ │ │ └── local │ │ │ ├── heros.yml │ │ │ └── heros │ │ │ └── About Hero.yml │ │ ├── global.yml │ │ └── global │ │ ├── Heros.yml │ │ └── Heros │ │ └── Default Hero.yml │ ├── layouts │ ├── uniform-jss.yml │ └── uniform-jss │ │ └── Uniform JSS.yml │ ├── placeholders │ ├── uniform-jss.yml │ └── uniform-jss │ │ └── uniform-jss-content.yml │ ├── renderings │ ├── uniform-jss.yml │ └── uniform-jss │ │ ├── Hero.yml │ │ └── HeroImage.yml │ └── templates │ ├── uniform-jss.yml │ └── uniform-jss │ ├── Components.yml │ ├── Components │ ├── Hero.yml │ └── Hero │ │ ├── Call to action.yml │ │ ├── Call to action │ │ ├── primaryCTALink.yml │ │ ├── primaryCTATitle.yml │ │ ├── secondaryCTALink.yml │ │ └── secondaryCTATitle.yml │ │ ├── Content.yml │ │ ├── Content │ │ ├── image.yml │ │ ├── subtitle.yml │ │ ├── text.yml │ │ └── title.yml │ │ └── __Standard Values.yml │ ├── Pages.yml │ └── Pages │ ├── CommonPage.yml │ └── CommonPage │ └── __Standard Values.yml └── src ├── .babelrc ├── .env.sample ├── .gitignore ├── .npmrc ├── .prettierignore ├── .prettierrc ├── assets └── css │ └── tailwind.css ├── components ├── Footer │ └── Footer.vue ├── Hero │ ├── Hero.vue │ ├── MenuDesktop.vue │ └── MenuMobile.vue ├── HeroImage │ ├── HeroImage.vue │ ├── MenuDesktop.vue │ └── MenuMobile.vue ├── LanguageSwitcherSample.vue ├── LinkList │ └── LinkList.vue ├── Logo │ └── Logo.vue ├── NavLinks │ └── NavLinks.vue ├── NuxtLinkWrapper │ └── NuxtLinkWrapper.vue ├── RoutableSitecoreLink │ └── RoutableSitecoreLink.vue └── RouterNavigationSample.vue ├── data ├── content │ ├── .gitignore │ └── Global Heros │ │ └── Default Hero │ │ └── en.yml ├── context │ ├── about │ │ └── en.json │ └── en.json └── routes │ ├── about │ └── en.yml │ └── en.yml ├── deploy.js ├── jest.config.js ├── layouts ├── README.md ├── default.vue └── error.vue ├── lib └── layoutServiceUtils.js ├── modules ├── express │ └── initialize.js ├── jss │ ├── data-fetcher │ │ ├── axios-data-fetcher-plugin.js │ │ ├── initialize.js │ │ └── install-data-fetcher-plugin.js │ ├── disconnected-mode │ │ ├── getDisconnectedModeMiddlewares.js │ │ └── initialize.js │ ├── i18n │ │ ├── module.js │ │ └── sitecore-jss-i18n-plugin.js │ ├── rendering-host │ │ ├── initialize.js │ │ └── nuxt-jss-rendering-host-middleware.js │ ├── sitecore-proxy │ │ ├── create-sitecore-proxy-middleware.js │ │ ├── default-proxy-configuration.js │ │ ├── handle-proxy-response.js │ │ ├── initialize.js │ │ └── nuxt-jss-proxy-middleware.js │ ├── standard │ │ ├── configure-router.js │ │ ├── initialize.js │ │ ├── sitecore-jss-config-runtime-plugin.js │ │ └── sitecore-jss-placeholder-plugin.js │ └── tracking-api │ │ ├── initialize.js │ │ └── sitecore-jss-tracking-api-plugin.js └── uniform │ ├── disconnected-export │ └── initialize.js │ └── services │ ├── initialize.js │ └── logging │ └── consoleLogger.js ├── nuxt.config.js ├── package-lock.json ├── package.json ├── pages ├── README.md └── _.vue ├── plugins └── export-route-data-context-plugin.js ├── scripts ├── bootstrap.js ├── generate-component-factory.js └── generate-config.js ├── server ├── server.config.js ├── server.js └── tunnel.js ├── sitecore ├── .gitignore ├── config │ └── uniform-jss.config ├── definitions │ ├── components │ │ └── Hero.sitecore.js │ ├── config.js │ ├── content.sitecore.js │ ├── dictionary.sitecore.js │ ├── placeholders.sitecore.js │ └── routes.sitecore.js └── pipelines │ └── generateMedia.patch.js ├── static ├── README.md └── favicon.ico ├── store ├── app │ └── index.js ├── index.js └── ui │ └── index.js ├── tailwind.config.js ├── temp └── .gitignore └── uniform.config.js /docs/images/azure-static-endpoint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uniformdev/sitecore-jss-nuxtjs-starterkit/f1e2ea584da52e02fb48af6297811da5abb88bf3/docs/images/azure-static-endpoint.png -------------------------------------------------------------------------------- /docs/images/azure-static-website.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uniformdev/sitecore-jss-nuxtjs-starterkit/f1e2ea584da52e02fb48af6297811da5abb88bf3/docs/images/azure-static-website.png -------------------------------------------------------------------------------- /docs/images/netlify-build-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uniformdev/sitecore-jss-nuxtjs-starterkit/f1e2ea584da52e02fb48af6297811da5abb88bf3/docs/images/netlify-build-settings.png -------------------------------------------------------------------------------- /docs/images/netlify-env-vars.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uniformdev/sitecore-jss-nuxtjs-starterkit/f1e2ea584da52e02fb48af6297811da5abb88bf3/docs/images/netlify-env-vars.png -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | base = "src/" 3 | publish = "out/" 4 | command = "npm run export" -------------------------------------------------------------------------------- /sitecore/Uniform SitecoreJSS Demo-items.0.0.1.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uniformdev/sitecore-jss-nuxtjs-starterkit/f1e2ea584da52e02fb48af6297811da5abb88bf3/sitecore/Uniform SitecoreJSS Demo-items.0.0.1.zip -------------------------------------------------------------------------------- /sitecore/config/uniform-jss.config: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 24 | 25 | 26 | 27 | 36 | 44 | 45 | 46 | 47 | 56 | 64 | 65 | 77 | 78 | 79 | 80 | mw=100,mh=50 81 | 82 | 83 | mw=300 84 | mw=100 85 | 86 | 87 | 88 | 89 | 94 | 95 | 96 | 97 | $(url) 98 | 99 | true 100 | true 101 | 102 | 103 | false 104 | false 105 | false 106 | false 107 | true 108 | 109 | 110 | 111 | 112 | 113 | context 114 | 115 | /sitecore/templates/Project/uniform-jss 116 | 117 | 118 | 119 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | -------------------------------------------------------------------------------- /sitecore/config/uniform-jss.unicorn.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | /unicorn.aspx 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /sitecore/serialization/content/uniform-jss.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ID: "b506a73e-c9bb-4c13-8f2c-836b1e9f59dc" 3 | Parent: "0de95ae4-41ab-4d01-9eb0-67441b7c2450" 4 | Template: "061cba15-5474-4b91-8a06-17903b102b82" 5 | Path: "/sitecore/content/uniform-jss" 6 | DB: master 7 | SharedFields: 8 | - ID: "06d5295c-ed2f-4a54-9bf2-26228d113318" 9 | Hint: __Icon 10 | Value: 11 | Languages: 12 | - Language: en 13 | Fields: 14 | - ID: "b5e02ad9-d56f-4c41-a065-a133db87bdeb" 15 | Hint: __Display name 16 | Value: Uniform (JSS) 17 | Versions: 18 | - Version: 1 19 | Fields: 20 | - ID: "25bed78c-4957-4165-998a-ca1b52f67497" 21 | Hint: __Created 22 | Value: 20200214T190709Z 23 | -------------------------------------------------------------------------------- /sitecore/serialization/content/uniform-jss/Home.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ID: "5505b7b1-b454-459e-9d36-06a4d89755ad" 3 | Parent: "b506a73e-c9bb-4c13-8f2c-836b1e9f59dc" 4 | Template: "bc93942a-b0e5-4bd3-a872-a9b8b5c7d4f9" 5 | Path: "/sitecore/content/uniform-jss/Home" 6 | DB: master 7 | SharedFields: 8 | - ID: "06d5295c-ed2f-4a54-9bf2-26228d113318" 9 | Hint: __Icon 10 | Value: Network/16x16/home.png 11 | - ID: "1172f251-dad4-4efb-a329-0c63500e4f1e" 12 | Hint: __Masters 13 | Type: TreelistEx 14 | Value: "{A65EAFF8-35C8-4929-A4A3-D2543B2867EA}" 15 | - ID: "a4f985d9-98b3-4b52-aaaf-4344f6e747c6" 16 | Hint: __Workflow 17 | Value: "{A5BC37E7-ED96-4C1E-8590-A26E64DB55EA}" 18 | - ID: "f1a1fe9e-a60c-4ddb-a3a0-bb5b29fe732e" 19 | Hint: __Renderings 20 | Type: layout 21 | Value: | 22 | 24 | 26 | 32 | 33 | 34 | Languages: 35 | - Language: en 36 | Versions: 37 | - Version: 1 38 | Fields: 39 | - ID: "25bed78c-4957-4165-998a-ca1b52f67497" 40 | Hint: __Created 41 | Value: 20200214T190718Z 42 | - ID: "3e431de1-525e-47a3-b6b0-1ccbec3a8c98" 43 | Hint: __Workflow state 44 | Value: "{190B1C84-F1BE-47ED-AA41-F42193D9C8FC}" 45 | -------------------------------------------------------------------------------- /sitecore/serialization/content/uniform-jss/Home/About.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ID: "0faa6199-bb54-455b-bacc-3b018c3fac1e" 3 | Parent: "5505b7b1-b454-459e-9d36-06a4d89755ad" 4 | Template: "bc93942a-b0e5-4bd3-a872-a9b8b5c7d4f9" 5 | Path: "/sitecore/content/uniform-jss/Home/About" 6 | DB: master 7 | SharedFields: 8 | - ID: "f1a1fe9e-a60c-4ddb-a3a0-bb5b29fe732e" 9 | Hint: __Renderings 10 | Type: layout 11 | Value: | 12 | 14 | 16 | 22 | 23 | 24 | Languages: 25 | - Language: en 26 | Versions: 27 | - Version: 1 28 | Fields: 29 | - ID: "25bed78c-4957-4165-998a-ca1b52f67497" 30 | Hint: __Created 31 | Value: 20200228T213909Z 32 | -------------------------------------------------------------------------------- /sitecore/serialization/content/uniform-jss/Home/About/local.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ID: "143c02e9-41e5-4803-a702-42d3bf01c141" 3 | Parent: "0faa6199-bb54-455b-bacc-3b018c3fac1e" 4 | Template: "a87a00b1-e6db-45ab-8b54-636fec3b5523" 5 | Path: "/sitecore/content/uniform-jss/Home/About/local" 6 | DB: master 7 | SharedFields: 8 | - ID: "06d5295c-ed2f-4a54-9bf2-26228d113318" 9 | Hint: __Icon 10 | Value: business/16x16/index.png 11 | Languages: 12 | - Language: en 13 | Fields: 14 | - ID: "b5e02ad9-d56f-4c41-a065-a133db87bdeb" 15 | Hint: __Display name 16 | Value: Local Page Content 17 | Versions: 18 | - Version: 1 19 | Fields: 20 | - ID: "25bed78c-4957-4165-998a-ca1b52f67497" 21 | Hint: __Created 22 | Value: 20200304T235806Z 23 | -------------------------------------------------------------------------------- /sitecore/serialization/content/uniform-jss/Home/About/local/heros.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ID: "f5461277-f3b0-44c3-bbb0-1b38ccb6709a" 3 | Parent: "143c02e9-41e5-4803-a702-42d3bf01c141" 4 | Template: "a87a00b1-e6db-45ab-8b54-636fec3b5523" 5 | Path: "/sitecore/content/uniform-jss/Home/About/local/heros" 6 | DB: master 7 | SharedFields: 8 | - ID: "06d5295c-ed2f-4a54-9bf2-26228d113318" 9 | Hint: __Icon 10 | Value: business/16x16/index.png 11 | - ID: "1172f251-dad4-4efb-a329-0c63500e4f1e" 12 | Hint: __Masters 13 | Type: TreelistEx 14 | Value: "{DD6D4B50-2296-4146-9F48-7C61FF989E07}" 15 | Languages: 16 | - Language: en 17 | Fields: 18 | - ID: "b5e02ad9-d56f-4c41-a065-a133db87bdeb" 19 | Hint: __Display name 20 | Value: Local Page Heros 21 | Versions: 22 | - Version: 1 23 | Fields: 24 | - ID: "25bed78c-4957-4165-998a-ca1b52f67497" 25 | Hint: __Created 26 | Value: 20200304T235839Z 27 | -------------------------------------------------------------------------------- /sitecore/serialization/content/uniform-jss/Home/About/local/heros/About Hero.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ID: "da5c6f8a-5cbd-4bb3-b7dd-ce8e866ab407" 3 | Parent: "f5461277-f3b0-44c3-bbb0-1b38ccb6709a" 4 | Template: "dd6d4b50-2296-4146-9f48-7c61ff989e07" 5 | Path: "/sitecore/content/uniform-jss/Home/About/local/heros/About Hero" 6 | DB: master 7 | Languages: 8 | - Language: en 9 | Versions: 10 | - Version: 1 11 | Fields: 12 | - ID: "0e14385e-493c-478e-8c3c-beb0839b5df3" 13 | Hint: text 14 | Value: Connecting the two worlds of Enterprise DXP and JAMstack 15 | - ID: "20753c20-34e3-4a03-8f34-1232250ae849" 16 | Hint: subtitle 17 | Value: Uniform 18 | - ID: "25bed78c-4957-4165-998a-ca1b52f67497" 19 | Hint: __Created 20 | Value: 20200304T235910Z 21 | - ID: "a4e63708-f7f5-4c53-9348-31e8b9a1b1e8" 22 | Hint: title 23 | Value: About 24 | - ID: "e928db78-cc24-4e20-beaa-e8e6956283f1" 25 | Hint: image 26 | Value: "https://images.unsplash.com/photo-1472148083604-64f1084980b9?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1200&q=80" 27 | -------------------------------------------------------------------------------- /sitecore/serialization/content/uniform-jss/global.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ID: "46a00631-b1dc-4545-935e-ff0e3225ba7c" 3 | Parent: "b506a73e-c9bb-4c13-8f2c-836b1e9f59dc" 4 | Template: "a87a00b1-e6db-45ab-8b54-636fec3b5523" 5 | Path: "/sitecore/content/uniform-jss/global" 6 | DB: master 7 | SharedFields: 8 | - ID: "06d5295c-ed2f-4a54-9bf2-26228d113318" 9 | Hint: __Icon 10 | Value: Network/16x16/environment.png 11 | - ID: "ba3f86a2-4a1c-4d78-b63d-91c2779c1b5e" 12 | Hint: __Sortorder 13 | Value: 200 14 | Languages: 15 | - Language: en 16 | Fields: 17 | - ID: "b5e02ad9-d56f-4c41-a065-a133db87bdeb" 18 | Hint: __Display name 19 | Value: Global Content 20 | Versions: 21 | - Version: 1 22 | Fields: 23 | - ID: "25bed78c-4957-4165-998a-ca1b52f67497" 24 | Hint: __Created 25 | Value: 20200228T212621Z 26 | -------------------------------------------------------------------------------- /sitecore/serialization/content/uniform-jss/global/Heros.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ID: "085c4ffa-12ec-41d4-b95b-a78f8492434b" 3 | Parent: "46a00631-b1dc-4545-935e-ff0e3225ba7c" 4 | Template: "a87a00b1-e6db-45ab-8b54-636fec3b5523" 5 | Path: "/sitecore/content/uniform-jss/global/Heros" 6 | DB: master 7 | SharedFields: 8 | - ID: "06d5295c-ed2f-4a54-9bf2-26228d113318" 9 | Hint: __Icon 10 | Value: network/16x16/environment.png 11 | - ID: "1172f251-dad4-4efb-a329-0c63500e4f1e" 12 | Hint: __Masters 13 | Type: TreelistEx 14 | Value: "{CE957D4A-5770-492C-A61A-695293C3F941}" 15 | Languages: 16 | - Language: en 17 | Fields: 18 | - ID: "b5e02ad9-d56f-4c41-a065-a133db87bdeb" 19 | Hint: __Display name 20 | Value: Global Heros 21 | Versions: 22 | - Version: 1 23 | Fields: 24 | - ID: "25bed78c-4957-4165-998a-ca1b52f67497" 25 | Hint: __Created 26 | Value: 20200228T212630Z 27 | -------------------------------------------------------------------------------- /sitecore/serialization/content/uniform-jss/global/Heros/Default Hero.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ID: "bb13d540-3676-4d38-b6cf-7724a17a2f46" 3 | Parent: "085c4ffa-12ec-41d4-b95b-a78f8492434b" 4 | Template: "dd6d4b50-2296-4146-9f48-7c61ff989e07" 5 | Path: "/sitecore/content/uniform-jss/global/Heros/Default Hero" 6 | DB: master 7 | SharedFields: 8 | - ID: "a4f985d9-98b3-4b52-aaaf-4344f6e747c6" 9 | Hint: __Workflow 10 | Value: "{A5BC37E7-ED96-4C1E-8590-A26E64DB55EA}" 11 | Languages: 12 | - Language: en 13 | Versions: 14 | - Version: 1 15 | Fields: 16 | - ID: "0e14385e-493c-478e-8c3c-beb0839b5df3" 17 | Hint: text 18 | Value: Uniform helps connect your existing digital marketing stack and JAMstackify it. 19 | - ID: "14f5cbb0-bafd-4414-a740-8e85d1fc6a66" 20 | Hint: secondaryCTALink 21 | Value: | 22 | 23 | - ID: "15bc044d-e93e-4139-ad94-9f4d141fcaaa" 24 | Hint: secondaryCTATitle 25 | Value: Schedule live demo 26 | - ID: "20218d3b-770b-4c46-90a8-7acba5136204" 27 | Hint: primaryCTALink 28 | Value: | 29 | 30 | - ID: "20753c20-34e3-4a03-8f34-1232250ae849" 31 | Hint: subtitle 32 | Value: to Uniform 33 | - ID: "25bed78c-4957-4165-998a-ca1b52f67497" 34 | Hint: __Created 35 | Value: 20200214T191703Z 36 | - ID: "3e431de1-525e-47a3-b6b0-1ccbec3a8c98" 37 | Hint: __Workflow state 38 | Value: "{190B1C84-F1BE-47ED-AA41-F42193D9C8FC}" 39 | - ID: "a4e63708-f7f5-4c53-9348-31e8b9a1b1e8" 40 | Hint: title 41 | Value: Welcome 42 | - ID: "e928db78-cc24-4e20-beaa-e8e6956283f1" 43 | Hint: image 44 | Value: 45 | - ID: "f64d1b7f-b721-43d3-b8b9-37270f765c0c" 46 | Hint: primaryCTATitle 47 | Value: Learn more 48 | -------------------------------------------------------------------------------- /sitecore/serialization/layouts/uniform-jss.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ID: "e9330290-a76c-4c69-bbfc-2245bab3f1f4" 3 | Parent: "da04b275-8838-4a3a-afee-817cf1fdd2eb" 4 | Template: "93227c5d-4fef-474d-94c0-f252ec8e8219" 5 | Path: "/sitecore/layout/Layouts/Project/uniform-jss" 6 | DB: master 7 | Languages: 8 | - Language: en 9 | Versions: 10 | - Version: 1 11 | Fields: 12 | - ID: "25bed78c-4957-4165-998a-ca1b52f67497" 13 | Hint: __Created 14 | Value: 20200302T030423Z 15 | -------------------------------------------------------------------------------- /sitecore/serialization/layouts/uniform-jss/Uniform JSS.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ID: "bdaa4402-015a-4f42-bf43-d5e083a02a20" 3 | Parent: "e9330290-a76c-4c69-bbfc-2245bab3f1f4" 4 | Template: "35c61e90-47dd-43dd-83a8-d1c4d5119720" 5 | Path: "/sitecore/layout/Layouts/Project/uniform-jss/Uniform JSS" 6 | DB: master 7 | SharedFields: 8 | - ID: "80334869-86dc-4472-aa89-44cf1b2f6c9b" 9 | Hint: Placeholders 10 | Type: Treelist 11 | Value: "{DEE69BFC-9B92-473B-802A-16DAFF595011}" 12 | Languages: 13 | - Language: en 14 | Versions: 15 | - Version: 1 16 | Fields: 17 | - ID: "25bed78c-4957-4165-998a-ca1b52f67497" 18 | Hint: __Created 19 | Value: 20200302T030433Z 20 | -------------------------------------------------------------------------------- /sitecore/serialization/placeholders/uniform-jss.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ID: "d098f13c-4629-48bf-8e85-e5812649face" 3 | Parent: "f5f0fbe3-61ad-4967-a5d8-8d760331d6a1" 4 | Template: "a87a00b1-e6db-45ab-8b54-636fec3b5523" 5 | Path: "/sitecore/layout/Placeholder Settings/Project/uniform-jss" 6 | DB: master 7 | Languages: 8 | - Language: en 9 | Versions: 10 | - Version: 1 11 | Fields: 12 | - ID: "25bed78c-4957-4165-998a-ca1b52f67497" 13 | Hint: __Created 14 | Value: 20200228T212816Z 15 | -------------------------------------------------------------------------------- /sitecore/serialization/placeholders/uniform-jss/uniform-jss-content.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ID: "dee69bfc-9b92-473b-802a-16daff595011" 3 | Parent: "d098f13c-4629-48bf-8e85-e5812649face" 4 | Template: "5c547d4e-7111-4995-95b0-6b561751bf2e" 5 | Path: "/sitecore/layout/Placeholder Settings/Project/uniform-jss/uniform-jss-content" 6 | DB: master 7 | SharedFields: 8 | - ID: "7256bdab-1fd2-49dd-b205-cb4873d2917c" 9 | Hint: Placeholder Key 10 | Value: "uniform-jss-content" 11 | - ID: "e391b526-d0c5-439d-803e-17512eae6222" 12 | Hint: Allowed Controls 13 | Type: TreelistEx 14 | Value: | 15 | {FB53DB9E-75B0-4DBD-A14B-3BD053ECEF59} 16 | {BDAFEC7C-B6C0-4E7F-B5FC-FC18D4F841F7} 17 | Languages: 18 | - Language: da 19 | Fields: 20 | - ID: "87871ff5-1965-46d6-884f-01d6a0b9c4c1" 21 | Hint: Description 22 | Value: | 23 |
24 | Indholdsplaceholder 25 |
26 | 27 |
28 | Sample Rendering 29 |
Indholdsplaceholderen er den primære placeholder.
30 |
31 | - ID: "b5e02ad9-d56f-4c41-a065-a133db87bdeb" 32 | Hint: __Display name 33 | Value: indhold 34 | Versions: 35 | - Version: 1 36 | Fields: 37 | - ID: "25bed78c-4957-4165-998a-ca1b52f67497" 38 | Hint: __Created 39 | Value: 20190328T131427Z 40 | - ID: "5dd74568-4d4b-44c1-b513-0af5f4cda34f" 41 | Hint: __Created by 42 | Value: | 43 | sitecore\Admin 44 | - Language: "de-DE" 45 | Fields: 46 | - ID: "87871ff5-1965-46d6-884f-01d6a0b9c4c1" 47 | Hint: Description 48 | Value: | 49 |
50 | Inhaltsplatzhalter 51 |
52 | 53 |
54 | Sample Rendering 55 |
Der Inhaltsplatzhalter ist der Hauptplatzhalter.
56 |
57 | - ID: "b5e02ad9-d56f-4c41-a065-a133db87bdeb" 58 | Hint: __Display name 59 | Value: Inhalt 60 | Versions: 61 | - Version: 1 62 | Fields: 63 | - ID: "25bed78c-4957-4165-998a-ca1b52f67497" 64 | Hint: __Created 65 | Value: 20190328T132519Z 66 | - ID: "5dd74568-4d4b-44c1-b513-0af5f4cda34f" 67 | Hint: __Created by 68 | Value: | 69 | sitecore\Admin 70 | - Language: en 71 | Fields: 72 | - ID: "87871ff5-1965-46d6-884f-01d6a0b9c4c1" 73 | Hint: Description 74 | Value: | 75 |
76 | Uniform JSS demo Placeholder 77 |
78 |
79 | Sample Rendering 80 |
The content placeholder is the main placeholder. 81 |
 
82 |
83 |
84 |
85 |
 
86 |
87 | Versions: 88 | - Version: 1 89 | Fields: 90 | - ID: "25bed78c-4957-4165-998a-ca1b52f67497" 91 | Hint: __Created 92 | Value: 20080110T175900Z 93 | - ID: "5dd74568-4d4b-44c1-b513-0af5f4cda34f" 94 | Hint: __Created by 95 | Value: | 96 | sitecore\Admin 97 | - Language: "ja-JP" 98 | Fields: 99 | - ID: "87871ff5-1965-46d6-884f-01d6a0b9c4c1" 100 | Hint: Description 101 | Value: | 102 |
103 | コンテンツ プレースホルダー 104 |
105 | 106 |
107 | Sample Rendering 108 |
コンテンツ プレースホルダーはメインのプレースホルダーです。
109 |
110 | - ID: "b5e02ad9-d56f-4c41-a065-a133db87bdeb" 111 | Hint: __Display name 112 | Value: コンテンツ (content) 113 | Versions: 114 | - Version: 1 115 | Fields: 116 | - ID: "25bed78c-4957-4165-998a-ca1b52f67497" 117 | Hint: __Created 118 | Value: 20190328T133902Z 119 | - ID: "5dd74568-4d4b-44c1-b513-0af5f4cda34f" 120 | Hint: __Created by 121 | Value: | 122 | sitecore\Admin 123 | - Language: "zh-CN" 124 | Fields: 125 | - ID: "87871ff5-1965-46d6-884f-01d6a0b9c4c1" 126 | Hint: Description 127 | Value: | 128 |
129 | 内容占位符 130 |
131 | 132 |
133 | Sample Rendering 134 |
此内容占位符是主要的占位符。
135 |
136 | - ID: "b5e02ad9-d56f-4c41-a065-a133db87bdeb" 137 | Hint: __Display name 138 | Value: 内容 139 | Versions: 140 | - Version: 1 141 | Fields: 142 | - ID: "25bed78c-4957-4165-998a-ca1b52f67497" 143 | Hint: __Created 144 | Value: 20190328T134752Z 145 | - ID: "5dd74568-4d4b-44c1-b513-0af5f4cda34f" 146 | Hint: __Created by 147 | Value: | 148 | sitecore\Admin 149 | -------------------------------------------------------------------------------- /sitecore/serialization/renderings/uniform-jss.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ID: "408a7178-2c9a-4d99-8853-2c0a170b616c" 3 | Parent: "1995806f-0a84-42b5-93b0-88f0e2ff872c" 4 | Template: "7ee0975b-0698-493e-b3a2-0b2ef33d0522" 5 | Path: "/sitecore/layout/Renderings/Project/uniform-jss" 6 | DB: master 7 | Languages: 8 | - Language: en 9 | Versions: 10 | - Version: 1 11 | Fields: 12 | - ID: "25bed78c-4957-4165-998a-ca1b52f67497" 13 | Hint: __Created 14 | Value: 20200214T191139Z 15 | -------------------------------------------------------------------------------- /sitecore/serialization/renderings/uniform-jss/Hero.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ID: "fb53db9e-75b0-4dbd-a14b-3bd053ecef59" 3 | Parent: "408a7178-2c9a-4d99-8853-2c0a170b616c" 4 | Template: "04646a89-996f-4ee7-878a-ffdbf1f0ef0d" 5 | Path: "/sitecore/layout/Renderings/Project/uniform-jss/Hero" 6 | DB: master 7 | SharedFields: 8 | - ID: "003a72cd-4cd6-4392-9862-41d4159929cd" 9 | Hint: Data source 10 | Value: 11 | - ID: "037fe404-dd19-4bf7-8e30-4dadf68b27b0" 12 | Hint: componentName 13 | Value: Hero 14 | - ID: "06d5295c-ed2f-4a54-9bf2-26228d113318" 15 | Hint: __Icon 16 | Value: software/16x16/component_yellow.png 17 | - ID: "1a7c85e5-dc0b-490d-9187-bb1dbcb4c72f" 18 | Hint: Datasource Template 19 | Value: "/sitecore/templates/Project/uniform-jss/Components/Hero" 20 | - ID: "7d8ae35f-9ed1-43b5-96a2-0a5f040d4e4e" 21 | Hint: Open Properties after Add 22 | Value: 1 23 | - ID: "b5b27af1-25ef-405c-87ce-369b3a004016" 24 | Hint: Datasource Location 25 | Value: "/sitecore/content/uniform-jss/global/heros|./local/heros" 26 | - ID: "e441abe7-2ca3-4640-ae26-3789967925d7" 27 | Hint: Compatible Renderings 28 | Type: Treelist 29 | Value: 30 | Languages: 31 | - Language: en 32 | Versions: 33 | - Version: 1 34 | Fields: 35 | - ID: "25bed78c-4957-4165-998a-ca1b52f67497" 36 | Hint: __Created 37 | Value: 20200214T190824Z 38 | -------------------------------------------------------------------------------- /sitecore/serialization/renderings/uniform-jss/HeroImage.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ID: "bdafec7c-b6c0-4e7f-b5fc-fc18d4f841f7" 3 | Parent: "408a7178-2c9a-4d99-8853-2c0a170b616c" 4 | Template: "04646a89-996f-4ee7-878a-ffdbf1f0ef0d" 5 | Path: "/sitecore/layout/Renderings/Project/uniform-jss/HeroImage" 6 | DB: master 7 | SharedFields: 8 | - ID: "003a72cd-4cd6-4392-9862-41d4159929cd" 9 | Hint: Data source 10 | Value: 11 | - ID: "037fe404-dd19-4bf7-8e30-4dadf68b27b0" 12 | Hint: componentName 13 | Value: HeroImage 14 | - ID: "06d5295c-ed2f-4a54-9bf2-26228d113318" 15 | Hint: __Icon 16 | Value: Software/16x16/component_yellow.png 17 | - ID: "1a7c85e5-dc0b-490d-9187-bb1dbcb4c72f" 18 | Hint: Datasource Template 19 | Value: "/sitecore/templates/Project/uniform-jss/Components/Hero" 20 | - ID: "7d8ae35f-9ed1-43b5-96a2-0a5f040d4e4e" 21 | Hint: Open Properties after Add 22 | Value: 1 23 | - ID: "b5b27af1-25ef-405c-87ce-369b3a004016" 24 | Hint: Datasource Location 25 | Value: "/sitecore/content/uniform-jss/global/heros|./local/heros" 26 | - ID: "e441abe7-2ca3-4640-ae26-3789967925d7" 27 | Hint: Compatible Renderings 28 | Type: Treelist 29 | Value: 30 | Languages: 31 | - Language: en 32 | Fields: 33 | - ID: "b5e02ad9-d56f-4c41-a065-a133db87bdeb" 34 | Hint: __Display name 35 | Value: Hero with Image 36 | Versions: 37 | - Version: 1 38 | Fields: 39 | - ID: "25bed78c-4957-4165-998a-ca1b52f67497" 40 | Hint: __Created 41 | Value: 20200214T190824Z 42 | -------------------------------------------------------------------------------- /sitecore/serialization/templates/uniform-jss.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ID: "dd99a4cb-f8cb-4764-a1f9-274c950173fc" 3 | Parent: "825b30b4-b40b-422e-9920-23a1b6bda89c" 4 | Template: "0437fee2-44c9-46a6-abe9-28858d9fee8c" 5 | Path: "/sitecore/templates/Project/uniform-jss" 6 | DB: master 7 | Languages: 8 | - Language: en 9 | Versions: 10 | - Version: 1 11 | Fields: 12 | - ID: "25bed78c-4957-4165-998a-ca1b52f67497" 13 | Hint: __Created 14 | Value: 20200219T020039Z 15 | -------------------------------------------------------------------------------- /sitecore/serialization/templates/uniform-jss/Components.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ID: "2150aa09-85f3-47a4-826b-f03b468d0fd6" 3 | Parent: "dd99a4cb-f8cb-4764-a1f9-274c950173fc" 4 | Template: "0437fee2-44c9-46a6-abe9-28858d9fee8c" 5 | Path: "/sitecore/templates/Project/uniform-jss/Components" 6 | DB: master 7 | Languages: 8 | - Language: en 9 | Versions: 10 | - Version: 1 11 | Fields: 12 | - ID: "25bed78c-4957-4165-998a-ca1b52f67497" 13 | Hint: __Created 14 | Value: 20200219T020056Z 15 | -------------------------------------------------------------------------------- /sitecore/serialization/templates/uniform-jss/Components/Hero.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ID: "dd6d4b50-2296-4146-9f48-7c61ff989e07" 3 | Parent: "2150aa09-85f3-47a4-826b-f03b468d0fd6" 4 | Template: "ab86861a-6030-46c5-b394-e8f99e8b87db" 5 | Path: "/sitecore/templates/Project/uniform-jss/Components/Hero" 6 | DB: master 7 | SharedFields: 8 | - ID: "06d5295c-ed2f-4a54-9bf2-26228d113318" 9 | Hint: __Icon 10 | Value: Network/16x16/id_card.png 11 | - ID: "12c33f3f-86c5-43a5-aeb4-5598cec45116" 12 | Hint: __Base template 13 | Type: tree list 14 | Value: "{1930BBEB-7805-471A-A3BE-4858AC7CF696}" 15 | - ID: "f7d48a55-2158-4f02-9356-756654404f73" 16 | Hint: __Standard values 17 | Value: "{908A3C35-455F-4EAC-9C93-B87C7BBD6311}" 18 | Languages: 19 | - Language: en 20 | Versions: 21 | - Version: 1 22 | Fields: 23 | - ID: "25bed78c-4957-4165-998a-ca1b52f67497" 24 | Hint: __Created 25 | Value: 20200219T020101Z 26 | -------------------------------------------------------------------------------- /sitecore/serialization/templates/uniform-jss/Components/Hero/Call to action.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ID: "47bac466-3abf-487e-b014-5e19ea237ff3" 3 | Parent: "dd6d4b50-2296-4146-9f48-7c61ff989e07" 4 | Template: "e269fbb5-3750-427a-9149-7aa950b49301" 5 | Path: "/sitecore/templates/Project/uniform-jss/Components/Hero/Call to action" 6 | DB: master 7 | SharedFields: 8 | - ID: "ba3f86a2-4a1c-4d78-b63d-91c2779c1b5e" 9 | Hint: __Sortorder 10 | Value: 200 11 | Languages: 12 | - Language: en 13 | Versions: 14 | - Version: 1 15 | Fields: 16 | - ID: "25bed78c-4957-4165-998a-ca1b52f67497" 17 | Hint: __Created 18 | Value: 20200229T003600Z 19 | -------------------------------------------------------------------------------- /sitecore/serialization/templates/uniform-jss/Components/Hero/Call to action/primaryCTALink.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ID: "20218d3b-770b-4c46-90a8-7acba5136204" 3 | Parent: "47bac466-3abf-487e-b014-5e19ea237ff3" 4 | Template: "455a3e98-a627-4b40-8035-e683a0331ac7" 5 | Path: "/sitecore/templates/Project/uniform-jss/Components/Hero/Call to action/primaryCTALink" 6 | DB: master 7 | SharedFields: 8 | - ID: "ab162cc0-dc80-4abf-8871-998ee5d7ba32" 9 | Hint: Type 10 | Value: General Link 11 | - ID: "ba3f86a2-4a1c-4d78-b63d-91c2779c1b5e" 12 | Hint: __Sortorder 13 | Value: 100 14 | Languages: 15 | - Language: en 16 | Versions: 17 | - Version: 1 18 | Fields: 19 | - ID: "25bed78c-4957-4165-998a-ca1b52f67497" 20 | Hint: __Created 21 | Value: 20200229T003601Z 22 | -------------------------------------------------------------------------------- /sitecore/serialization/templates/uniform-jss/Components/Hero/Call to action/primaryCTATitle.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ID: "f64d1b7f-b721-43d3-b8b9-37270f765c0c" 3 | Parent: "47bac466-3abf-487e-b014-5e19ea237ff3" 4 | Template: "455a3e98-a627-4b40-8035-e683a0331ac7" 5 | Path: "/sitecore/templates/Project/uniform-jss/Components/Hero/Call to action/primaryCTATitle" 6 | DB: master 7 | SharedFields: 8 | - ID: "ab162cc0-dc80-4abf-8871-998ee5d7ba32" 9 | Hint: Type 10 | Value: "Single-Line Text" 11 | - ID: "ba3f86a2-4a1c-4d78-b63d-91c2779c1b5e" 12 | Hint: __Sortorder 13 | Value: 200 14 | Languages: 15 | - Language: en 16 | Versions: 17 | - Version: 1 18 | Fields: 19 | - ID: "25bed78c-4957-4165-998a-ca1b52f67497" 20 | Hint: __Created 21 | Value: 20200229T003601Z 22 | -------------------------------------------------------------------------------- /sitecore/serialization/templates/uniform-jss/Components/Hero/Call to action/secondaryCTALink.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ID: "14f5cbb0-bafd-4414-a740-8e85d1fc6a66" 3 | Parent: "47bac466-3abf-487e-b014-5e19ea237ff3" 4 | Template: "455a3e98-a627-4b40-8035-e683a0331ac7" 5 | Path: "/sitecore/templates/Project/uniform-jss/Components/Hero/Call to action/secondaryCTALink" 6 | DB: master 7 | SharedFields: 8 | - ID: "ab162cc0-dc80-4abf-8871-998ee5d7ba32" 9 | Hint: Type 10 | Value: General Link 11 | - ID: "ba3f86a2-4a1c-4d78-b63d-91c2779c1b5e" 12 | Hint: __Sortorder 13 | Value: 300 14 | Languages: 15 | - Language: en 16 | Versions: 17 | - Version: 1 18 | Fields: 19 | - ID: "25bed78c-4957-4165-998a-ca1b52f67497" 20 | Hint: __Created 21 | Value: 20200229T003601Z 22 | -------------------------------------------------------------------------------- /sitecore/serialization/templates/uniform-jss/Components/Hero/Call to action/secondaryCTATitle.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ID: "15bc044d-e93e-4139-ad94-9f4d141fcaaa" 3 | Parent: "47bac466-3abf-487e-b014-5e19ea237ff3" 4 | Template: "455a3e98-a627-4b40-8035-e683a0331ac7" 5 | Path: "/sitecore/templates/Project/uniform-jss/Components/Hero/Call to action/secondaryCTATitle" 6 | DB: master 7 | SharedFields: 8 | - ID: "ab162cc0-dc80-4abf-8871-998ee5d7ba32" 9 | Hint: Type 10 | Value: "Single-Line Text" 11 | - ID: "ba3f86a2-4a1c-4d78-b63d-91c2779c1b5e" 12 | Hint: __Sortorder 13 | Value: 400 14 | Languages: 15 | - Language: en 16 | Versions: 17 | - Version: 1 18 | Fields: 19 | - ID: "25bed78c-4957-4165-998a-ca1b52f67497" 20 | Hint: __Created 21 | Value: 20200229T003601Z 22 | -------------------------------------------------------------------------------- /sitecore/serialization/templates/uniform-jss/Components/Hero/Content.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ID: "4fc5ab9d-579c-4564-b1d5-4d115703ca56" 3 | Parent: "dd6d4b50-2296-4146-9f48-7c61ff989e07" 4 | Template: "e269fbb5-3750-427a-9149-7aa950b49301" 5 | Path: "/sitecore/templates/Project/uniform-jss/Components/Hero/Content" 6 | DB: master 7 | Languages: 8 | - Language: en 9 | Versions: 10 | - Version: 1 11 | Fields: 12 | - ID: "25bed78c-4957-4165-998a-ca1b52f67497" 13 | Hint: __Created 14 | Value: 20200228T212354Z 15 | -------------------------------------------------------------------------------- /sitecore/serialization/templates/uniform-jss/Components/Hero/Content/image.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ID: "e928db78-cc24-4e20-beaa-e8e6956283f1" 3 | Parent: "4fc5ab9d-579c-4564-b1d5-4d115703ca56" 4 | Template: "455a3e98-a627-4b40-8035-e683a0331ac7" 5 | Path: "/sitecore/templates/Project/uniform-jss/Components/Hero/Content/image" 6 | DB: master 7 | SharedFields: 8 | - ID: "ab162cc0-dc80-4abf-8871-998ee5d7ba32" 9 | Hint: Type 10 | Value: "Single-Line Text" 11 | - ID: "ba3f86a2-4a1c-4d78-b63d-91c2779c1b5e" 12 | Hint: __Sortorder 13 | Value: 300 14 | Languages: 15 | - Language: en 16 | Versions: 17 | - Version: 1 18 | Fields: 19 | - ID: "25bed78c-4957-4165-998a-ca1b52f67497" 20 | Hint: __Created 21 | Value: 20200229T011120Z 22 | -------------------------------------------------------------------------------- /sitecore/serialization/templates/uniform-jss/Components/Hero/Content/subtitle.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ID: "20753c20-34e3-4a03-8f34-1232250ae849" 3 | Parent: "4fc5ab9d-579c-4564-b1d5-4d115703ca56" 4 | Template: "455a3e98-a627-4b40-8035-e683a0331ac7" 5 | Path: "/sitecore/templates/Project/uniform-jss/Components/Hero/Content/subtitle" 6 | DB: master 7 | SharedFields: 8 | - ID: "ab162cc0-dc80-4abf-8871-998ee5d7ba32" 9 | Hint: Type 10 | Value: "Single-Line Text" 11 | - ID: "ba3f86a2-4a1c-4d78-b63d-91c2779c1b5e" 12 | Hint: __Sortorder 13 | Value: 150 14 | Languages: 15 | - Language: en 16 | Versions: 17 | - Version: 1 18 | Fields: 19 | - ID: "25bed78c-4957-4165-998a-ca1b52f67497" 20 | Hint: __Created 21 | Value: 20200229T000037Z 22 | -------------------------------------------------------------------------------- /sitecore/serialization/templates/uniform-jss/Components/Hero/Content/text.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ID: "0e14385e-493c-478e-8c3c-beb0839b5df3" 3 | Parent: "4fc5ab9d-579c-4564-b1d5-4d115703ca56" 4 | Template: "455a3e98-a627-4b40-8035-e683a0331ac7" 5 | Path: "/sitecore/templates/Project/uniform-jss/Components/Hero/Content/text" 6 | DB: master 7 | SharedFields: 8 | - ID: "ab162cc0-dc80-4abf-8871-998ee5d7ba32" 9 | Hint: Type 10 | Value: Rich Text 11 | - ID: "ba3f86a2-4a1c-4d78-b63d-91c2779c1b5e" 12 | Hint: __Sortorder 13 | Value: 200 14 | Languages: 15 | - Language: en 16 | Versions: 17 | - Version: 1 18 | Fields: 19 | - ID: "25bed78c-4957-4165-998a-ca1b52f67497" 20 | Hint: __Created 21 | Value: 20200228T212354Z 22 | -------------------------------------------------------------------------------- /sitecore/serialization/templates/uniform-jss/Components/Hero/Content/title.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ID: "a4e63708-f7f5-4c53-9348-31e8b9a1b1e8" 3 | Parent: "4fc5ab9d-579c-4564-b1d5-4d115703ca56" 4 | Template: "455a3e98-a627-4b40-8035-e683a0331ac7" 5 | Path: "/sitecore/templates/Project/uniform-jss/Components/Hero/Content/title" 6 | DB: master 7 | SharedFields: 8 | - ID: "ab162cc0-dc80-4abf-8871-998ee5d7ba32" 9 | Hint: Type 10 | Value: "Single-Line Text" 11 | - ID: "ba3f86a2-4a1c-4d78-b63d-91c2779c1b5e" 12 | Hint: __Sortorder 13 | Value: 100 14 | Languages: 15 | - Language: en 16 | Versions: 17 | - Version: 1 18 | Fields: 19 | - ID: "25bed78c-4957-4165-998a-ca1b52f67497" 20 | Hint: __Created 21 | Value: 20200228T212354Z 22 | -------------------------------------------------------------------------------- /sitecore/serialization/templates/uniform-jss/Components/Hero/__Standard Values.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ID: "908a3c35-455f-4eac-9c93-b87c7bbd6311" 3 | Parent: "dd6d4b50-2296-4146-9f48-7c61ff989e07" 4 | Template: "dd6d4b50-2296-4146-9f48-7c61ff989e07" 5 | Path: "/sitecore/templates/Project/uniform-jss/Components/Hero/__Standard Values" 6 | DB: master 7 | Languages: 8 | - Language: en 9 | Versions: 10 | - Version: 1 11 | Fields: 12 | - ID: "25bed78c-4957-4165-998a-ca1b52f67497" 13 | Hint: __Created 14 | Value: 20200228T212356Z 15 | - ID: "5dd74568-4d4b-44c1-b513-0af5f4cda34f" 16 | Hint: __Created by 17 | Value: | 18 | sitecore\Admin 19 | - ID: "a4e63708-f7f5-4c53-9348-31e8b9a1b1e8" 20 | Hint: title 21 | Value: $name 22 | -------------------------------------------------------------------------------- /sitecore/serialization/templates/uniform-jss/Pages.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ID: "97bcb300-2ce0-4040-98e4-9f55acd310c1" 3 | Parent: "dd99a4cb-f8cb-4764-a1f9-274c950173fc" 4 | Template: "0437fee2-44c9-46a6-abe9-28858d9fee8c" 5 | Path: "/sitecore/templates/Project/uniform-jss/Pages" 6 | DB: master 7 | Languages: 8 | - Language: en 9 | Versions: 10 | - Version: 1 11 | Fields: 12 | - ID: "25bed78c-4957-4165-998a-ca1b52f67497" 13 | Hint: __Created 14 | Value: 20200219T020106Z 15 | -------------------------------------------------------------------------------- /sitecore/serialization/templates/uniform-jss/Pages/CommonPage.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ID: "bc93942a-b0e5-4bd3-a872-a9b8b5c7d4f9" 3 | Parent: "97bcb300-2ce0-4040-98e4-9f55acd310c1" 4 | Template: "ab86861a-6030-46c5-b394-e8f99e8b87db" 5 | Path: "/sitecore/templates/Project/uniform-jss/Pages/CommonPage" 6 | DB: master 7 | SharedFields: 8 | - ID: "06d5295c-ed2f-4a54-9bf2-26228d113318" 9 | Hint: __Icon 10 | Value: Applications/16x16/document_plain.png 11 | - ID: "12c33f3f-86c5-43a5-aeb4-5598cec45116" 12 | Hint: __Base template 13 | Type: tree list 14 | Value: | 15 | {1930BBEB-7805-471A-A3BE-4858AC7CF696} 16 | {B36BA9FD-0DC0-49C8-BEA2-E55D70E6AF28} 17 | - ID: "f7d48a55-2158-4f02-9356-756654404f73" 18 | Hint: __Standard values 19 | Value: "{E87F5226-5089-4D69-8B16-ABE69B134125}" 20 | Languages: 21 | - Language: en 22 | Versions: 23 | - Version: 1 24 | Fields: 25 | - ID: "25bed78c-4957-4165-998a-ca1b52f67497" 26 | Hint: __Created 27 | Value: 20200219T020047Z 28 | -------------------------------------------------------------------------------- /sitecore/serialization/templates/uniform-jss/Pages/CommonPage/__Standard Values.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ID: "e87f5226-5089-4d69-8b16-abe69b134125" 3 | Parent: "bc93942a-b0e5-4bd3-a872-a9b8b5c7d4f9" 4 | Template: "bc93942a-b0e5-4bd3-a872-a9b8b5c7d4f9" 5 | Path: "/sitecore/templates/Project/uniform-jss/Pages/CommonPage/__Standard Values" 6 | DB: master 7 | SharedFields: 8 | - ID: "f1a1fe9e-a60c-4ddb-a3a0-bb5b29fe732e" 9 | Hint: __Renderings 10 | Type: layout 11 | Value: | 12 | 13 | 16 | 17 | Languages: 18 | - Language: en 19 | Versions: 20 | - Version: 1 21 | Fields: 22 | - ID: "25bed78c-4957-4165-998a-ca1b52f67497" 23 | Hint: __Created 24 | Value: 20200228T213729Z 25 | - ID: "5dd74568-4d4b-44c1-b513-0af5f4cda34f" 26 | Hint: __Created by 27 | Value: | 28 | sitecore\Admin 29 | -------------------------------------------------------------------------------- /src/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "test": { 4 | "presets": [ 5 | [ 6 | "@babel/preset-env", 7 | { 8 | "targets": { 9 | "node": "current" 10 | } 11 | } 12 | ] 13 | ] 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/.env.sample: -------------------------------------------------------------------------------- 1 | UNIFORM_API_KEY={layout-service-api-key-here} 2 | UNIFORM_API_URL=https://uniform-jss-nuxt.dev.local 3 | 4 | # important: make sure these values are set as provided below 5 | UNIFORM_API_SITENAME='uniform-jss-kit' 6 | UNIFORM_API_MAPSERVICE='/uniform/api/content/${UNIFORM_API_SITENAME}/map' 7 | 8 | # Helps resolve connection issue enable if connecting to Sitecore over HTTPS with dev certs to avoid Node.js connection issues 9 | NODE_TLS_REJECT_UNAUTHORIZED=0 -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Node template 3 | # Logs 4 | logs 5 | *.log 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | 10 | # Runtime data 11 | pids 12 | *.pid 13 | *.seed 14 | *.pid.lock 15 | 16 | # Directory for instrumented libs generated by jscoverage/JSCover 17 | lib-cov 18 | 19 | # Coverage directory used by tools like istanbul 20 | coverage 21 | 22 | # nyc test coverage 23 | .nyc_output 24 | 25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 26 | .grunt 27 | 28 | # Bower dependency directory (https://bower.io/) 29 | bower_components 30 | 31 | # node-waf configuration 32 | .lock-wscript 33 | 34 | # Compiled binary addons (https://nodejs.org/api/addons.html) 35 | build/Release 36 | 37 | # Dependency directories 38 | node_modules/ 39 | jspm_packages/ 40 | 41 | # Temporarily local private deps 42 | !data/node_modules/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # Optional npm cache directory 48 | .npm 49 | 50 | # Optional eslint cache 51 | .eslintcache 52 | 53 | # Optional REPL history 54 | .node_repl_history 55 | 56 | # Output of 'npm pack' 57 | *.tgz 58 | 59 | # Yarn Integrity file 60 | .yarn-integrity 61 | 62 | # dotenv environment variables file 63 | .env 64 | 65 | # parcel-bundler cache (https://parceljs.org/) 66 | .cache 67 | 68 | # next.js build output 69 | .next 70 | 71 | # nuxt.js build output 72 | .nuxt 73 | 74 | # Nuxt generate 75 | ./dist 76 | /out 77 | 78 | # vuepress build output 79 | .vuepress/dist 80 | 81 | # Serverless directories 82 | .serverless 83 | 84 | # IDE / Editor 85 | .idea 86 | .editorconfig 87 | 88 | # Service worker 89 | sw.* 90 | 91 | # Mac OSX 92 | .DS_Store 93 | 94 | # Vim swap files 95 | *.swp 96 | /scjssconfig.json 97 | 98 | netlify.config.json 99 | 100 | # Uniform temp folder 101 | .temp -------------------------------------------------------------------------------- /src/.npmrc: -------------------------------------------------------------------------------- 1 | //registry.npmjs.org/:_authToken=${NPM_TOKEN} -------------------------------------------------------------------------------- /src/.prettierignore: -------------------------------------------------------------------------------- 1 | package.json 2 | -------------------------------------------------------------------------------- /src/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "es5", 4 | "tabWidth": 2, 5 | "arrowParens": "always", 6 | "printWidth": 100, 7 | "endOfLine": "auto" 8 | } 9 | -------------------------------------------------------------------------------- /src/assets/css/tailwind.css: -------------------------------------------------------------------------------- 1 | @import 'tailwindcss/base'; 2 | @import 'tailwindcss/components'; 3 | @import 'tailwindcss/utilities'; 4 | -------------------------------------------------------------------------------- /src/components/Footer/Footer.vue: -------------------------------------------------------------------------------- 1 | 32 | -------------------------------------------------------------------------------- /src/components/Hero/Hero.vue: -------------------------------------------------------------------------------- 1 | 154 | 155 | 206 | -------------------------------------------------------------------------------- /src/components/Hero/MenuDesktop.vue: -------------------------------------------------------------------------------- 1 | 45 | 46 | 67 | -------------------------------------------------------------------------------- /src/components/Hero/MenuMobile.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 65 | -------------------------------------------------------------------------------- /src/components/HeroImage/HeroImage.vue: -------------------------------------------------------------------------------- 1 | 68 | 69 | 118 | -------------------------------------------------------------------------------- /src/components/HeroImage/MenuDesktop.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 55 | -------------------------------------------------------------------------------- /src/components/HeroImage/MenuMobile.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 65 | -------------------------------------------------------------------------------- /src/components/LanguageSwitcherSample.vue: -------------------------------------------------------------------------------- 1 | 10 | 23 | -------------------------------------------------------------------------------- /src/components/LinkList/LinkList.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 18 | -------------------------------------------------------------------------------- /src/components/Logo/Logo.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 44 | -------------------------------------------------------------------------------- /src/components/NavLinks/NavLinks.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 18 | -------------------------------------------------------------------------------- /src/components/NuxtLinkWrapper/NuxtLinkWrapper.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 39 | -------------------------------------------------------------------------------- /src/components/RoutableSitecoreLink/RoutableSitecoreLink.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 48 | -------------------------------------------------------------------------------- /src/components/RouterNavigationSample.vue: -------------------------------------------------------------------------------- 1 | 28 | -------------------------------------------------------------------------------- /src/data/content/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uniformdev/sitecore-jss-nuxtjs-starterkit/f1e2ea584da52e02fb48af6297811da5abb88bf3/src/data/content/.gitignore -------------------------------------------------------------------------------- /src/data/content/Global Heros/Default Hero/en.yml: -------------------------------------------------------------------------------- 1 | id: global-default-hero 2 | name: Default Hero 3 | template: Hero 4 | fields: 5 | title: Welcome 6 | subtitle: to Uniform 7 | text: Uniform helps connect your existing digital marketing stack and JAMstackify it. 8 | image: 9 | primaryCTALink: 10 | href: http://uniform.dev 11 | primaryCTATitle: Learn more 12 | secondaryCTALink: 13 | href: https://calendly.com/uniformdemo/intro 14 | secondaryCTATitle: Schedule live demo 15 | -------------------------------------------------------------------------------- /src/data/context/about/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "pageEditing": false, 3 | "site": { "name": "website" }, 4 | "pageState": "normal", 5 | "language": "en", 6 | "personalization": { 7 | "rules": { 8 | "70886a78-86e8-48b8-b023-46d0816c8400": [ 9 | { 10 | "id": "8563e333-ef77-46e4-b267-a1f0fa067413", 11 | "name": "Bluekai rule", 12 | "condition": "return (function(){var c=(a, b)=>(a && b) ? a.toLowerCase().indexOf(b.toLowerCase()) != -1 : false; var m = document.cookie.match(new RegExp('(^| )uniform.bluekai=([^;]+)')); if (m) { return c(m[2], 'Sitecore+-+Business+Traveler+Audience')}return false;})();", 13 | "data": "320c2c78-d198-42d7-b69e-39f00134bca5", 14 | "component": "fb53db9e-75b0-4dbd-a14b-3bd053ecef59" 15 | }, 16 | { 17 | "id": null, 18 | "name": "Default", 19 | "condition": "return true;", 20 | "data": "global-default-hero", 21 | "component": "fb53db9e-75b0-4dbd-a14b-3bd053ecef59" 22 | } 23 | ] 24 | }, 25 | "data": { 26 | "320c2c78-d198-42d7-b69e-39f00134bca5": { 27 | "subtitle": { "value": "BlueKai cookie rule data source" }, 28 | "title": { "value": "(About page) Hero for BlueKai cookie" }, 29 | "text": { 30 | "value": "This is the data source set on the rule that depends on a BlueKai cookie being set." 31 | } 32 | }, 33 | "global-default-hero": { 34 | "subtitle": { "value": "Uniform" }, 35 | "title": { "value": "About" }, 36 | "text": { "value": "Connecting the two worlds of Enterprise DXP and JAMstack" }, 37 | "image": { 38 | "value": "https://images.unsplash.com/photo-1472148083604-64f1084980b9?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1200&q=80" 39 | } 40 | } 41 | } 42 | }, 43 | "tracking": { 44 | "campaigns": { 45 | "f5e4b73b-9843-44db-9ce7-bc372440d74b": { "name": "Test Campaign" } 46 | }, 47 | "events": { 48 | "35f570b4-5ea6-4313-9653-3787966ea377": { 49 | "name": "Business Travel Page Viewed", 50 | "points": 0.0 51 | } 52 | }, 53 | "profiles": { 54 | "24dff2cf-b30a-4b75-8967-2fe3ded82271": { 55 | "name": "Focus", 56 | "keys": { 57 | "03379af5-f1ae-4610-b15b-4c7f1032b464": { 58 | "key": "Background", 59 | "value": 1.0 60 | }, 61 | "5fdd9829-e689-454d-9abc-8f95ae68744c": { 62 | "key": "Practical", 63 | "value": 2.0 64 | }, 65 | "f5652c06-676b-4e12-a9d0-06d000e5f1c8": { 66 | "key": "Process", 67 | "value": 3.0 68 | }, 69 | "b32bfacc-3494-4127-b050-cf50078e2b4c": { 70 | "key": "Scope", 71 | "value": 4.0 72 | } 73 | }, 74 | "total": 10.0 75 | } 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/data/context/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "pageEditing": false, 3 | "site": { "name": "website" }, 4 | "pageState": "normal", 5 | "language": "en", 6 | "personalization": { 7 | "rules": { 8 | "70886a78-86e8-48b8-b023-46d0816c8400": [ 9 | { 10 | "id": "8563e333-ef77-46e4-b267-a1f0fa067413", 11 | "name": "Bluekai rule", 12 | "condition": "return (function(){var c=(a, b)=>(a && b) ? a.toLowerCase().indexOf(b.toLowerCase()) != -1 : false; var m = document.cookie.match(new RegExp('(^| )uniform.bluekai=([^;]+)')); if (m) { return c(m[2], 'Sitecore+-+Business+Traveler+Audience')}return false;})();", 13 | "data": "320c2c78-d198-42d7-b69e-39f00134bca5", 14 | "component": "fb53db9e-75b0-4dbd-a14b-3bd053ecef59" 15 | }, 16 | { 17 | "id": "54c3e333-ef77-46e4-b267-a1f0fa067413", 18 | "name": "Query string rule", 19 | "condition": "return new URLSearchParams(window.location.search).has(\"uniform-parameter\");", 20 | "data": "d50afb41-e95d-40d2-b0a8-b5d318d00dd2", 21 | "component": "fb53db9e-75b0-4dbd-a14b-3bd053ecef59" 22 | }, 23 | { 24 | "id": null, 25 | "name": "Default", 26 | "condition": "return true;", 27 | "data": "global-default-hero", 28 | "component": "fb53db9e-75b0-4dbd-a14b-3bd053ecef59" 29 | } 30 | ] 31 | }, 32 | "data": { 33 | "320c2c78-d198-42d7-b69e-39f00134bca5": { 34 | "subtitle": { "value": "BlueKai cookie rule data source" }, 35 | "title": { "value": "Hero for BlueKai cookie" }, 36 | "text": { 37 | "value": "This is the data source set on the rule that depends on a BlueKai cookie being set." 38 | } 39 | }, 40 | "d50afb41-e95d-40d2-b0a8-b5d318d00dd2": { 41 | "subtitle": { "value": "Query string parameter rule data source" }, 42 | "title": { "value": "Hero for query string" }, 43 | "text": { 44 | "value": "This is the data source set on the rule that depends on the query string parameter uniform-parameter being set." 45 | } 46 | }, 47 | "global-default-hero": { 48 | "subtitle": { "value": "Rendering data source" }, 49 | "title": { "value": "Hero set on rendering" }, 50 | "text": { "value": "This is the data source set on the rendering." } 51 | } 52 | } 53 | }, 54 | "tracking": { 55 | "campaigns": { 56 | "f5e4b73b-9843-44db-9ce7-bc372440d74b": { "name": "Test Campaign" } 57 | }, 58 | "events": { 59 | "35f570b4-5ea6-4313-9653-3787966ea377": { 60 | "name": "Business Travel Page Viewed", 61 | "points": 0.0 62 | } 63 | }, 64 | "profiles": { 65 | "24dff2cf-b30a-4b75-8967-2fe3ded82271": { 66 | "name": "Focus", 67 | "keys": { 68 | "03379af5-f1ae-4610-b15b-4c7f1032b464": { 69 | "key": "Background", 70 | "value": 1.0 71 | }, 72 | "5fdd9829-e689-454d-9abc-8f95ae68744c": { 73 | "key": "Practical", 74 | "value": 2.0 75 | }, 76 | "f5652c06-676b-4e12-a9d0-06d000e5f1c8": { 77 | "key": "Process", 78 | "value": 3.0 79 | }, 80 | "b32bfacc-3494-4127-b050-cf50078e2b4c": { 81 | "key": "Scope", 82 | "value": 4.0 83 | } 84 | }, 85 | "total": 10.0 86 | } 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/data/routes/about/en.yml: -------------------------------------------------------------------------------- 1 | template: CommonPage 2 | name: About 3 | placeholders: 4 | uniform-jss-kit-content: 5 | - componentName: Hero 6 | id: global-default-hero-2 7 | uid: 70886a78-86e8-48b8-b023-46d0816c8400 8 | fields: 9 | title: About 10 | subtitle: Uniform 11 | text: Connecting the two worlds of Enterprise DXP and JAMstack 12 | image: https://images.unsplash.com/photo-1472148083604-64f1084980b9?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1200&q=80 13 | -------------------------------------------------------------------------------- /src/data/routes/en.yml: -------------------------------------------------------------------------------- 1 | # This is a route layout definition. 2 | # The route definition defines which Sitecore components are present on a route, 3 | # what their content data is, and which _placeholder_ they are placed in. 4 | 5 | # This particular route definition is for the home route - '/', so it defines the 6 | # components shown on the initial page of the app. 7 | 8 | # You may use equivalent JSON files instead of YAML if you prefer; 9 | # however YAML is simpler to read and allows comments like this one :) 10 | 11 | # Setting an ID is optional, but it will allow referring to this item in internal links 12 | # the ID can be a app-wide-unique string, or a GUID value. 13 | id: home-page 14 | name: Home 15 | template: CommonPage 16 | icon: Network/16x16/home.png 17 | 18 | # Define the page layout starting at the root placeholder - in this case, 'uniform-jss-kit-content' 19 | # root placeholder names are defined in the package.json config section (required for Sitecore deployment) 20 | placeholders: 21 | uniform-jss-kit-content: 22 | - id: global-default-hero 23 | uid: 70886a78-86e8-48b8-b023-46d0816c8400 24 | -------------------------------------------------------------------------------- /src/deploy.js: -------------------------------------------------------------------------------- 1 | const dotenv = require('dotenv'); 2 | dotenv.config(); 3 | const { createPublishProvider } = require('@uniformdev/publishing-all'); 4 | createPublishProvider().deploy('out'); 5 | -------------------------------------------------------------------------------- /src/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | moduleNameMapper: { 3 | '^@/(.*)$': '/$1', 4 | '^~/(.*)$': '/$1', 5 | '^vue$': 'vue/dist/vue.common.js', 6 | }, 7 | moduleFileExtensions: ['js', 'vue', 'json'], 8 | transform: { 9 | '^.+\\.js$': 'babel-jest', 10 | '.*\\.(vue)$': 'vue-jest', 11 | }, 12 | collectCoverage: true, 13 | collectCoverageFrom: ['/components/**/*.vue', '/pages/**/*.vue'], 14 | }; 15 | -------------------------------------------------------------------------------- /src/layouts/README.md: -------------------------------------------------------------------------------- 1 | # LAYOUTS 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | This directory contains your Application Layouts. 6 | 7 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/views#layouts). 8 | -------------------------------------------------------------------------------- /src/layouts/default.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 16 | -------------------------------------------------------------------------------- /src/layouts/error.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 37 | -------------------------------------------------------------------------------- /src/lib/layoutServiceUtils.js: -------------------------------------------------------------------------------- 1 | import { dataApi } from '@sitecore-jss/sitecore-jss-vue'; 2 | 3 | export function createLayoutServiceClient(config, { nuxtContext } = {}) { 4 | return { 5 | getRouteData: (route, language) => getRouteData(route, language, config, nuxtContext), 6 | }; 7 | } 8 | 9 | function getRouteData(route, language, config, nuxtContext) { 10 | const fetchOptions = { 11 | // NOTE: we want to proxy client-side layout service requests through the Nuxt server so 12 | // that cookies are properly maintained. Therefore, we set the Layout Service 13 | // config `host` value to empty when in a client context. 14 | // The Nuxt server or hosting platform (for static sites) will then handle requests for `/sitecore/api/layout/...` 15 | layoutServiceConfig: { host: process.client ? '' : config.sitecoreApiHost }, 16 | querystringParams: { 17 | sc_lang: language, 18 | sc_apikey: config.sitecoreApiKey, 19 | sc_site: config.sitecoreSiteName, 20 | }, 21 | fetcher: nuxtContext.$jss.dataFetcher, 22 | }; 23 | 24 | // NOTE: using `!process.client` will help ensure dead code elimination during minification for the client bundle. 25 | if ( 26 | !process.client && 27 | nuxtContext?.app?.context?.isStatic && 28 | nuxtContext?.app?.getExportRouteDataContext 29 | ) { 30 | // export mode 31 | // Fetch layout data from Layout Service, then write the data to disk. 32 | const { exportRouteDataWriter } = nuxtContext.app.getExportRouteDataContext(); 33 | let apiData; 34 | return fetchFromApi(route, fetchOptions) 35 | .then((data) => { 36 | apiData = data; 37 | return exportRouteDataWriter(route, language, data); 38 | }) 39 | .then(() => apiData); 40 | } else if (process.env.NODE_ENV === 'production') { 41 | // If we're in production mode and not running a static site, then fetch directly from API. 42 | if (!process.static) { 43 | return fetchFromApi(route, fetchOptions); 44 | } 45 | // production mode (i.e. the app is "running" somewhere) 46 | // Attempt to fetch layout data from disk, and fall back to Layout Service if disk fetch returns 404. 47 | return fetchFromDisk(route, language, fetchOptions.fetcher).catch((err) => { 48 | if (err.response && err.response.status === 404) { 49 | return fetchFromApi(route, fetchOptions); 50 | } 51 | console.error(err); 52 | }); 53 | } else { 54 | // development mode 55 | // Fetch layout data from Layout Service 56 | return fetchFromApi(route, fetchOptions); 57 | } 58 | } 59 | 60 | // note: if `str` is undefined, no leading slash will be applied/returned. 61 | function ensureLeadingSlash(str) { 62 | if (str && !str.startsWith('/')) { 63 | return `/${str}`; 64 | } 65 | return str; 66 | } 67 | 68 | function fetchFromDisk(route, language, dataFetcher) { 69 | let formattedRoute = ensureLeadingSlash(route); 70 | if (formattedRoute === '/') { 71 | formattedRoute = '/home'; 72 | } 73 | 74 | const filePath = `/data${formattedRoute}/${language}.json`; 75 | return dataFetcher(filePath).then((response) => { 76 | // note: `dataFetcher` returns the parsed response, but we're only interested in 77 | // the `data` property, which is what is returned by the `dataApi.fetchRouteData` function. 78 | return response.data; 79 | }); 80 | } 81 | 82 | function fetchFromApi(route, fetchOptions) { 83 | // Layout Service may resolve items (routes) incorrectly without a leading slash. This is 84 | // particularly true for nested routes, e.g. home/sub1/sub2/sub3. Therefore, we ensure 85 | // that the item/route being requested has a leading slash. 86 | const formattedRoute = ensureLeadingSlash(route); 87 | 88 | return dataApi.fetchRouteData(formattedRoute, fetchOptions); 89 | } 90 | -------------------------------------------------------------------------------- /src/modules/express/initialize.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const compression = require('compression'); 3 | 4 | module.exports = initialize; 5 | 6 | // Nuxt uses `connect` as a server when starting a Nuxt app. 7 | // This module "hacks" around the use of connect by injecting an `express` server 8 | // instance into the Nuxt server instance just prior to server startup, i.e. 9 | // in the `render:before` hook. 10 | function initialize(moduleOptions) { 11 | // Nuxt config is available via `this.options` 12 | const nuxtConfig = this.options; 13 | 14 | this.nuxt.hook('render:before', async (nuxtServer) => { 15 | // `connect` has a `stack` property that Nuxt uses for removing/replacing middleware. 16 | // `express` has a similar property but it is on the Express `router` object that is attached 17 | // to an express instance. 18 | // For compatibility, we use the Express `lazyrouter` method to ensure the `server._router` 19 | // property is initialized and defined. Then we add a `stack` property to the Express server 20 | // object and assign the Express router.stack property. 21 | // This seems to be sufficient to satisfy the Nuxt usage of connect. 22 | const expressApp = express(); 23 | expressApp.lazyrouter(); 24 | expressApp.stack = expressApp._router.stack; 25 | 26 | // Add compression middleware. 27 | expressApp.use(compression()); 28 | 29 | // Disable built-in Nuxt/connect compressor. Nuxt only activates compression for production mode. 30 | // We want to enable compression regardless of mode for consistency. In particular, there may 31 | // be other middleware that modify the response headers and content length. 32 | nuxtConfig.render = { 33 | ...nuxtConfig.render, 34 | compressor: null, 35 | }; 36 | 37 | // Assign the Express server/app to the `app` property on the Nuxt server instance. This will 38 | // replace the default `connect` app created by Nuxt. 39 | nuxtServer.app = expressApp; 40 | }); 41 | } 42 | -------------------------------------------------------------------------------- /src/modules/jss/data-fetcher/axios-data-fetcher-plugin.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { installDataFetcherPlugin } from '~/modules/jss/data-fetcher/install-data-fetcher-plugin'; 3 | 4 | export default function initialize(context) { 5 | // Create and install the plugin. 6 | installDataFetcherPlugin(dataFetcher, context); 7 | } 8 | 9 | /** 10 | * Implements a data fetcher using Axios - replace with your favorite 11 | * SSR-capable HTTP or fetch library if you like. See HttpJsonFetcher type 12 | * in sitecore-jss library for implementation details/notes. 13 | * @param {string} url The URL to request; may include query string 14 | * @param {any} data Optional data to POST with the request. 15 | */ 16 | function dataFetcher(url, data, options) { 17 | return axios({ 18 | url, 19 | method: data ? 'POST' : 'GET', 20 | data, 21 | // note: axios needs to use `withCredentials: true` in order for Sitecore cookies to be included in CORS requests 22 | // which is necessary for analytics and such 23 | withCredentials: true, 24 | ...options, 25 | }); 26 | } 27 | -------------------------------------------------------------------------------- /src/modules/jss/data-fetcher/initialize.js: -------------------------------------------------------------------------------- 1 | const nodePath = require('path'); 2 | 3 | module.exports = initialize; 4 | 5 | function initialize(moduleOptions) { 6 | switch (moduleOptions.dataFetcherType) { 7 | case 'axios': 8 | default: { 9 | this.addPlugin(nodePath.resolve(__dirname, 'axios-data-fetcher-plugin.js')); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/modules/jss/data-fetcher/install-data-fetcher-plugin.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | 3 | export function installDataFetcherPlugin(dataFetcher, nuxtContext) { 4 | // Define the plugin. 5 | const plugin = { 6 | install: (VueInstance) => 7 | install({ 8 | dataFetcher, 9 | VueInstance, 10 | nuxtContext, 11 | }), 12 | key: 'SitecoreJssDataFetcherPlugin', 13 | }; 14 | 15 | // Note: we don't use the built-in Nuxt plugin "inject" functionality because it 16 | // will assign the plugin to the Vue instance using the `key` property. However, we 17 | // want all JSS-related plugins to be accessible via the `$jss` property, so we 18 | // need to use the Vue-provided `Vue.use()` syntax to install JSS plugins. 19 | Vue.use(plugin); 20 | } 21 | 22 | function install({ dataFetcher, VueInstance, nuxtContext }) { 23 | if (nuxtContext.$jss && nuxtContext.$jss.dataFetcher) { 24 | console.log('JSS Data Fetcher plugin already installed.', nuxtContext.$jss.dataFetcher); 25 | return; 26 | } 27 | 28 | // Define properties that will be available in the `$jss` object. 29 | // Note: these properties are _not_ intended to be reactive, which is why they are 30 | // all functions - to help make it clear to devs that the properties are not reactive. 31 | const pluginProps = { 32 | dataFetcher, 33 | }; 34 | 35 | // Who knows what other JSS plugins are doing? So be kind and merge our plugin props 36 | // with any existing `$jss` object properties. 37 | nuxtContext.$jss = Object.assign(nuxtContext.$jss || {}, pluginProps); 38 | nuxtContext.app.$jss = Object.assign(nuxtContext.app.$jss || {}, pluginProps); 39 | VueInstance.prototype.$jss = Object.assign(VueInstance.prototype.$jss || {}, pluginProps); 40 | } 41 | -------------------------------------------------------------------------------- /src/modules/jss/disconnected-mode/getDisconnectedModeMiddlewares.js: -------------------------------------------------------------------------------- 1 | const serveStatic = require('serve-static'); 2 | const path = require('path'); 3 | const fs = require('fs'); 4 | const { 5 | createDisconnectedDictionaryService, 6 | ManifestManager, 7 | createDisconnectedLayoutService, 8 | } = require('@sitecore-jss/sitecore-jss-dev-tools'); 9 | 10 | module.exports = { 11 | getDisconnectedModeMiddlewares, 12 | }; 13 | 14 | async function getDisconnectedModeMiddlewares({ jssConfig }) { 15 | const touchToReloadFilePath = path.resolve(path.join(__dirname, '../../../temp/config.js')); 16 | 17 | const options = { 18 | appRoot: path.join(__dirname, '../../../'), 19 | appName: jssConfig.jssAppName, 20 | watchPaths: [path.join(__dirname, '../../../data')], 21 | language: jssConfig.defaultLanguage, 22 | onManifestUpdated: (manifest) => { 23 | // if we can resolve the config file, we can alter it to force reloading the app automatically 24 | // instead of waiting for a manual reload. We must materially alter the _contents_ of the file to trigger 25 | // an actual reload, so we append "// reloadnow" to the file each time. This will not cause a problem, 26 | // since every build regenerates the config file from scratch and it's ignored from source control. 27 | if (fs.existsSync(touchToReloadFilePath)) { 28 | const currentFileContents = fs.readFileSync(touchToReloadFilePath, 'utf8'); 29 | const newFileContents = `${currentFileContents}\n// reloadnow`; 30 | fs.writeFileSync(touchToReloadFilePath, newFileContents, 'utf8'); 31 | 32 | console.log('Manifest data updated. Reloading the browser.'); 33 | } else { 34 | console.log('Manifest data updated. Refresh the browser to see latest content!'); 35 | } 36 | }, 37 | }; 38 | 39 | // the manifest manager maintains the state of the disconnected manifest data during the course of the dev run 40 | // it provides file watching services, and language switching capabilities 41 | const manifestManager = new ManifestManager({ 42 | appName: options.appName, 43 | rootPath: options.appRoot, 44 | watchOnlySourceFiles: options.watchPaths, 45 | requireArg: options.requireArg, 46 | sourceFiles: options.sourceFiles, 47 | }); 48 | 49 | return manifestManager.getManifest(options.language).then((manifest) => { 50 | // creates a fake version of the Sitecore Layout Service that is powered by your disconnected manifest file 51 | const layoutService = createDisconnectedLayoutService({ 52 | manifest, 53 | manifestLanguageChangeCallback: manifestManager.getManifest, 54 | customizeContext: options.customizeContext, 55 | customizeRoute: options.customizeRoute, 56 | customizeRendering: options.customizeRendering, 57 | }); 58 | 59 | // creates a fake version of the Sitecore Dictionary Service that is powered by your disconnected manifest file 60 | const dictionaryService = createDisconnectedDictionaryService({ 61 | manifest, 62 | manifestLanguageChangeCallback: manifestManager.getManifest, 63 | }); 64 | 65 | // set up live reloading of the manifest when any manifest source file is changed 66 | manifestManager.setManifestUpdatedCallback((newManifest) => { 67 | layoutService.updateManifest(newManifest); 68 | dictionaryService.updateManifest(newManifest); 69 | if (options.onManifestUpdated) { 70 | options.onManifestUpdated(newManifest); 71 | } 72 | }); 73 | 74 | // define our disconnected service middlewares 75 | return [ 76 | { path: '/assets', handler: serveStatic(path.join(options.appRoot, 'assets')) }, 77 | { path: '/data/media', handler: serveStatic(path.join(options.appRoot, 'data/media')) }, 78 | { path: '/sitecore/api/layout/render', handler: layoutService.middleware }, 79 | { 80 | path: '/sitecore/api/jss/dictionary/:appName/:language', 81 | handler: dictionaryService.middleware, 82 | }, 83 | ]; 84 | }); 85 | } 86 | -------------------------------------------------------------------------------- /src/modules/jss/disconnected-mode/initialize.js: -------------------------------------------------------------------------------- 1 | // https://nuxtjs.org/guide/modules#write-a-basic-module 2 | const { getDisconnectedModeMiddlewares } = require('./getDisconnectedModeMiddlewares'); 3 | const { getConfig } = require('../../../temp/config'); 4 | 5 | module.exports = initialize; 6 | 7 | async function initialize(moduleOptions) { 8 | if (!moduleOptions.enabled) { 9 | return; 10 | } 11 | 12 | const middlewares = await getDisconnectedModeMiddlewares({ 13 | jssConfig: getConfig(), 14 | }); 15 | 16 | middlewares.forEach((middleware) => { 17 | this.addServerMiddleware(middleware); 18 | }); 19 | } 20 | -------------------------------------------------------------------------------- /src/modules/jss/i18n/module.js: -------------------------------------------------------------------------------- 1 | const nodePath = require('path'); 2 | 3 | module.exports = initialize; 4 | 5 | function initialize(moduleOptions) { 6 | if (!moduleOptions.enabled) { 7 | return; 8 | } 9 | 10 | this.addPlugin(nodePath.resolve(__dirname, 'sitecore-jss-i18n-plugin.js')); 11 | } 12 | -------------------------------------------------------------------------------- /src/modules/jss/i18n/sitecore-jss-i18n-plugin.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | 3 | export default function initialize(nuxtContext) { 4 | if (!nuxtContext.$jss || !nuxtContext.$jss.dataFetcher) { 5 | throw new Error( 6 | 'Unable to initialize the JSS i18n plugin. No `dataFetcher` instance was available. Ensure that a JSS `dataFetcher` module or plugin has been installed and initialized.' 7 | ); 8 | } 9 | if (!nuxtContext.$jss || !nuxtContext.$jss.getRuntimeConfig) { 10 | throw new Error( 11 | 'Unable to initialize the JSS Tracking API plugin. No `getRuntimeConfig` method was available. Ensure that a JSS `getRuntimeConfig` plugin has been installed and initialized.' 12 | ); 13 | } 14 | 15 | // Initialize i18n store first (so we can access the store during the init process). 16 | // Then resolve the state value (which may require an async fetch to retrieve dictionary data). 17 | // Then set the store value. 18 | // And finally, install the plugin. We install the plugin last because of the possible 19 | // async fetch to retrieve dictionary data. 20 | initializeI18nStore(nuxtContext.store); 21 | 22 | return resolveStateValue(nuxtContext) 23 | .then((state) => { 24 | // After successful i18n init, track the i18n values in state 25 | nuxtContext.store.commit('i18n/seti18n', state); 26 | }) 27 | .then(() => { 28 | // After successful i18n and store init, install the i18n plugin. 29 | const plugin = { 30 | install: (VueInstance) => install(VueInstance, nuxtContext), 31 | key: 'SitecoreJssi18nextPlugin', 32 | }; 33 | 34 | Vue.use(plugin); 35 | }); 36 | } 37 | 38 | function initializeI18nStore(store) { 39 | // Create and register a Vuex store for i18n state. 40 | const namespace = 'i18n'; 41 | 42 | const storeModule = { 43 | namespaced: true, 44 | state: () => ({ language: '', dictionary: null }), 45 | mutations: { 46 | seti18n(state, { language, dictionary }) { 47 | state.language = language; 48 | state.dictionary = dictionary; 49 | }, 50 | }, 51 | }; 52 | 53 | store.registerModule(namespace, storeModule, { 54 | preserveState: Boolean(store.state[namespace]), 55 | }); 56 | } 57 | 58 | async function resolveStateValue(nuxtContext) { 59 | const { req, params, store, $jss } = nuxtContext; 60 | 61 | const jssConfig = $jss.getRuntimeConfig(); 62 | 63 | // `req` is only defined for SSR. 64 | // If in SSR or static export, we need to initialize i18n using the route language parameter, 65 | // using the default language as fallback. 66 | if (req || nuxtContext?.app?.context?.isStatic) { 67 | // If the request has `jssData.viewBag` properties for language and dictionary, then 68 | // use the values to populate the state. This is primarily for JSS Rendering Host support. 69 | // NOTE: `req` is undefined during export/generate, so be sure to null-check it. 70 | if (req?.jssData?.viewBag?.language && req?.jssData?.viewBag?.dictionary) { 71 | return Promise.resolve({ 72 | language: req.jssData.viewBag.language, 73 | dictionary: req.jssData.viewBag.dictionary, 74 | }); 75 | } 76 | 77 | // Else assume we need to resolve the language and fetch dictionary data. 78 | const resolvedLanguage = params.lang || jssConfig.defaultLanguage; 79 | return fetchDictionaryData(resolvedLanguage, nuxtContext).then((dictionary) => { 80 | return { 81 | language: resolvedLanguage, 82 | dictionary, 83 | }; 84 | }); 85 | } else { 86 | // If not SSR, then assume we're hydrating from SSR state and use the language and dictionary 87 | // provided via SSR state. 88 | return Promise.resolve({ 89 | language: store.state.i18n.language, 90 | dictionary: store.state.i18n.dictionary, 91 | }); 92 | } 93 | } 94 | 95 | function install(VueInstance, nuxtContext) { 96 | if (nuxtContext.$jss?.getCurrentLanguage) { 97 | console.log('JSS i18n plugin already installed.'); 98 | return; 99 | } 100 | 101 | // Define properties that will be available in the `$jss` object. 102 | // Note: these properties are _not_ intended to be reactive, which is why they are 103 | // all functions - to help make it clear to devs that the properties are not reactive. 104 | const props = { 105 | getCurrentLanguage: () => nuxtContext.store.state.i18n.language, 106 | getCurrentDictionary: () => { 107 | return nuxtContext.store.state.i18n.dictionary; 108 | }, 109 | translate: (phrase) => nuxtContext.store.state.i18n.dictionary[phrase], 110 | changeLanguage: (newLanguage) => changeLanguage(newLanguage, nuxtContext), 111 | }; 112 | 113 | // Who knows what other JSS plugins are doing? So be kind and merge our plugin props 114 | // with any existing `$jss` object properties. 115 | nuxtContext.$jss = Object.assign(nuxtContext.$jss || {}, props); 116 | nuxtContext.app.$jss = Object.assign(nuxtContext.app.$jss || {}, props); 117 | VueInstance.prototype.$jss = Object.assign(VueInstance.prototype.$jss || {}, props); 118 | } 119 | 120 | function changeLanguage(newLanguage, nuxtContext) { 121 | // If the language hasn't changed, don't do anything. 122 | if (newLanguage === nuxtContext.store.state.i18n.language) { 123 | return Promise.resolve(false); 124 | } 125 | 126 | // Ftch new dictionary data and change the i18n state data. 127 | // Then change the app route so that the app can fetch Layout Service data for the new 128 | // language and then re-render. 129 | return fetchDictionaryData(newLanguage, nuxtContext).then((dictionary) => { 130 | nuxtContext.store.commit('i18n/seti18n', { 131 | language: newLanguage, 132 | dictionary, 133 | }); 134 | 135 | const newRoute = `/${newLanguage}${ 136 | nuxtContext.app.router.currentRoute.params.sitecoreRoute || '' 137 | }`; 138 | 139 | nuxtContext.app.router.push(newRoute); 140 | return true; 141 | }); 142 | } 143 | 144 | function fetchDictionaryData(language, nuxtContext) { 145 | if ( 146 | nuxtContext.store.state.i18n?.dictionary && 147 | language === nuxtContext.store.state.i18n.language 148 | ) { 149 | return Promise.resolve(nuxtContext.store.state.i18n.dictionary); 150 | } 151 | 152 | const jssConfig = nuxtContext.$jss.getRuntimeConfig(); 153 | const dictionaryServiceUrl = `${jssConfig.sitecoreApiHost}/sitecore/api/jss/dictionary/${jssConfig.jssAppName}/${language}?sc_apikey=${jssConfig.sitecoreApiKey}&sc_site=${jssConfig.sitecoreSiteName}`; 154 | 155 | return nuxtContext.$jss.dataFetcher(dictionaryServiceUrl).then((response) => { 156 | if (response.data && response.data.phrases) { 157 | return response.data.phrases; 158 | } 159 | return null; 160 | }); 161 | } 162 | -------------------------------------------------------------------------------- /src/modules/jss/rendering-host/initialize.js: -------------------------------------------------------------------------------- 1 | const bodyParser = require('body-parser'); 2 | const cors = require('cors'); 3 | const WebpackRequireFromPlugin = require('webpack-require-from'); 4 | const { getJssRenderingHostMiddleware } = require('./nuxt-jss-rendering-host-middleware'); 5 | // TODO: attach jss config to Nuxt config or module options 6 | const { getConfig: getJssConfigRenderingHost } = require('../../../temp/jss-config-rendering-host'); 7 | 8 | module.exports = initialize; 9 | 10 | function initialize(moduleOptions) { 11 | if (!moduleOptions.enabled) { 12 | return; 13 | } 14 | 15 | // In Nuxt modules, the current Nuxt instance is available via `this.nuxt`. 16 | const nuxtApp = this.nuxt; 17 | 18 | // In Nuxt modules, Nuxt config (nuxt.config.js) is available via `this.options`. 19 | const nuxtConfig = this.options; 20 | 21 | setupMiddleware(nuxtApp, moduleOptions); 22 | configureWebpack(nuxtConfig); 23 | configureHooks(nuxtApp, nuxtConfig, moduleOptions.resolveRenderingHostPublicUrl); 24 | } 25 | 26 | function setupMiddleware(nuxtApp, { renderingHostListenRoute }) { 27 | const jssConfig = getJssConfigRenderingHost(); 28 | if (!jssConfig) { 29 | console.warn( 30 | 'JSS config was not provided to JSS rendering host, therefore JSS rendering host middleware is disabled.' 31 | ); 32 | return; 33 | } 34 | 35 | const renderingHostConfig = { 36 | app: nuxtApp, 37 | jssConfig, 38 | }; 39 | 40 | // We use the `render:setupMiddleware` Nuxt hook instead of `this.addMiddleware` because using 41 | // `this.addMiddleware` results in a `server.use(middleware)` call. However, for rendering host 42 | // we specifically only want to add the middleware for POST requests to `renderingHostListenRoute`. 43 | // NOTE: the `render:setupMiddleware` hook is fired before any other middleware are registered. 44 | nuxtApp.hook('render:setupMiddleware', async (server) => { 45 | server.use(cors()); 46 | 47 | // Setup body parsing middleware for incoming POST requests. 48 | const jsonBodyParser = bodyParser.json({ 49 | limit: '2mb', 50 | }); 51 | 52 | // Setup the JSS rendering host route. 53 | // By default, Sitecore will use the `/jss-render` route, but this can be configured via 54 | // JSS app config, e.g. ``. 55 | // So we allow `renderingHostRoute` to be configured via `moduleOptions` so that devs 56 | // can configure the middleware to match the path configured in Sitecore. 57 | const resolvedRenderingHostListenRoute = renderingHostListenRoute || '/jss-render'; 58 | server.post( 59 | resolvedRenderingHostListenRoute, 60 | jsonBodyParser, 61 | getJssRenderingHostMiddleware(renderingHostConfig) 62 | ); 63 | }); 64 | } 65 | 66 | function configureWebpack(nuxtConfig) { 67 | // webpack-require-from plugin allows us to specify where the client-side webpack `publicPath` 68 | // property value should be read from. For JSS rendering host requests, the actual `publicPath` 69 | // property we need isn't known until runtime, so we inject the `__nuxt_public_path__` property 70 | // into the outgoing HTML for the rendering host request (see the `configureHooks` method below). 71 | // Then we use the webpack-require-from plugin to instruct Webpack to read from the 72 | // `window.__nuxt_public_path__` variable. 73 | // This allows requests for async webpack chunks (and anything else dependent on webpack publicPath) 74 | // to succeed because they'll use the absolute URL to our Nuxt server instead of a relative path 75 | // that will resolve to the Sitecore server. 76 | // NOTE: we use the `suppressErrors` option so that if `__nuxt_public_path__` is undefined on 77 | // the client, no console errors will be shown. This will happen for requests that are not JSS 78 | // rendering host requests. 79 | const webpackRequireFrom = new WebpackRequireFromPlugin({ 80 | variableName: '__nuxt_public_path__', 81 | suppressErrors: true, 82 | }); 83 | nuxtConfig.build.plugins = [...nuxtConfig.build.plugins, webpackRequireFrom]; 84 | 85 | // // Customize the `hotMiddleware` configuration so that requests to the HMR endpoint 86 | // // can succeed in both local development and in JSS rendering host. 87 | // // TODO: check https://github.com/nuxt/nuxt.js/pull/7318 to see if any changes need to be made 88 | // // to the below config. 89 | nuxtConfig.build.hotMiddleware = { 90 | ...nuxtConfig.build.hotMiddleware, 91 | // The `path` value below needs to match what Nuxt/webpack will emit as the URL for the webpack HMR script. 92 | // The `//` is intentional due to how Nuxt invokes the hotmiddleware. By default, Nuxt will 93 | // prepend the client path with `router.base`, which has a default value of `/`. Then the 94 | // `publicPath` gets prepended to the path, resulting in `{host}/_nuxt//__webpack_hmr/client`. 95 | path: '/_nuxt//__webpack_hmr/client', 96 | client: { 97 | ...nuxtConfig.build.hotMiddleware.client, 98 | // Set to true to use webpack publicPath as prefix of path. 99 | dynamicPublicPath: true, 100 | }, 101 | }; 102 | } 103 | 104 | function configureHooks(nuxtApp, nuxtConfig, resolveRenderingHostPublicUrl) { 105 | // When the rendering host middleware executes, it renders the Nuxt app and sends the generated 106 | // HTML back to Sitecore for serving. By default, all Nuxt/Webpack-generated assets are referenced 107 | // via relative URLs, e.g. `/_nuxt/runtime.js`. However, because the HTML will be served by Sitecore, 108 | // the HTML _must_ use absolute URLs to reference Nuxt/Webpack-generated assets, 109 | // e.g. `http://localhost:3000/_nuxt/runtime.js`, otherwise the browser won't be able to resolve the URLs. 110 | // 111 | // Nuxt/Webpack provide a setting named `publicPath` that normally solves this problem. If you provide 112 | // a value for `publicPath` it will be prepended to any URLs for Nuxt/Webpack-generated assets. 113 | // However, you can only set `publicPath` at build time or app start time. This is a problem for 114 | // JSS Rendering Host because the URL for the rendering host may not be known at build/start time 115 | // and the URL may be different from the URL that the Nuxt web server is running on. 116 | // 117 | // Therefore, we want to be able to both declare a `publicPath` and prepend asset URLs on a per-request basis. 118 | // To do this, we use a combination of webpack config modification (in the configureWebpack method above) as 119 | // well as intercepting `; 194 | 195 | templateParams.HEAD += publicPathScript; 196 | }); 197 | } 198 | -------------------------------------------------------------------------------- /src/modules/jss/rendering-host/nuxt-jss-rendering-host-middleware.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | getJssRenderingHostMiddleware, 3 | }; 4 | 5 | function getJssRenderingHostMiddleware({ app, jssConfig } = {}) { 6 | return async function middleware(req, res) { 7 | req.setTimeout(36000, () => { 8 | console.error('request timed out'); 9 | }); 10 | 11 | try { 12 | const jssData = resolveJssData(req); 13 | 14 | // Server rendering functions expect `GET` requests, but we're handling a `POST` request. 15 | // so change the incoming request method. 16 | req.method = 'GET'; 17 | // nuxt.js reads from the req.url property, so set it accordingly 18 | req.url = jssData.renderPath; 19 | 20 | console.log('rendering host handling request', req.url); 21 | 22 | // Allows the app to easily determine whether or not it is being rendered via JSS rendering host. 23 | req.isJssRenderingHostRequest = true; 24 | // Attach the parsed JSS data as an arbitrary property on the `req` object 25 | req.jssData = jssData; 26 | // Attach the JSS config values. These should be the _remote_ `sitecoreApiHost`, `sitecoreApiKey`, 27 | // and `sitecoreSiteName` values, not necessarily the same as the generated values from `temp/config.js`. 28 | // This allows us to override the generated config values at runtime. Otherwise, when the app is 29 | // served by the Sitecore server, the JSS app may be making layout service/dictionary service requests 30 | // to the `sitecoreApiHost` value specified in `temp/config.js`. And that value could be, 31 | // for example, `localhost:3000` if running the app in disconnected mode. That could result in 32 | // CORS errors and unexpected data. So, we provide the remote/"connected" mode config values so the app can 33 | // use those if/when necessary. 34 | if (jssConfig) { 35 | req.jssConfig = jssConfig; 36 | } 37 | 38 | const routeInfo = { 39 | pathname: jssData.renderPath, 40 | }; 41 | 42 | // render app and return 43 | // renderResult is an object: { html, error, redirected } 44 | // The object passed as the second argument to `renderRoute` will be added to the 45 | // Nuxt SSR `renderContext` object. The `req` and `res` properties are required, but 46 | // you can also add custom properties, e.g. `isJssRenderingHostRequest`, that will 47 | // be available anywhere you have access to the SSR `renderContext` object. 48 | const renderResult = await app.renderRoute(routeInfo.pathname, { 49 | req, 50 | res, 51 | isJssRenderingHostRequest: true, 52 | }); 53 | 54 | // TODO: need to handle 404 and/or redirect 55 | if (renderResult.error) { 56 | console.error(renderResult.error); 57 | } 58 | // TODO: need to handle 404 and/or redirect 59 | if (renderResult.redirected) { 60 | console.log('redirected', renderResult.redirected); 61 | } 62 | 63 | res.send({ 64 | html: renderResult.html, 65 | status: 200, 66 | redirect: '', 67 | }); 68 | } catch (err) { 69 | console.error(err); 70 | res.send({ 71 | html: `JSS app rendering error: ${err}`, 72 | status: 500, 73 | redirect: '', 74 | }); 75 | } 76 | }; 77 | } 78 | 79 | function resolveJssData(req) { 80 | // We assume req.body has already been parsed as JSON via something like `body-parser` middleware. 81 | const invocationInfo = req.body; 82 | 83 | // By default, the JSS server invokes this route with the following body data structure: 84 | // { 85 | // id: 'JSS app name', 86 | // args: ['route path', 'serialized layout data object', 'serialized viewbag object'], 87 | // functionName: 'renderView', 88 | // moduleName: 'server.bundle' 89 | // } 90 | 91 | const result = { 92 | route: null, 93 | viewBag: null, 94 | renderPath: '', 95 | }; 96 | 97 | if (!invocationInfo || !invocationInfo.args || !Array.isArray(invocationInfo.args)) { 98 | return result; 99 | } 100 | 101 | result.renderPath = invocationInfo.args[0]; 102 | 103 | if (invocationInfo.args.length > 0 && invocationInfo.args[1]) { 104 | result.route = tryParseJson(invocationInfo.args[1]); 105 | } 106 | 107 | if (invocationInfo.args.length > 1 && invocationInfo.args[2]) { 108 | result.viewBag = tryParseJson(invocationInfo.args[2]); 109 | } 110 | 111 | return result; 112 | } 113 | 114 | function tryParseJson(jsonString) { 115 | try { 116 | const json = JSON.parse(jsonString); 117 | // handle non-exception-throwing cases 118 | if (json && typeof json === 'object' && json !== null) { 119 | return json; 120 | } 121 | } catch (e) { 122 | console.error(`error parsing json string '${jsonString}'`, e); 123 | } 124 | 125 | return null; 126 | } 127 | -------------------------------------------------------------------------------- /src/modules/jss/sitecore-proxy/default-proxy-configuration.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | getProxyConfiguration, 3 | }; 4 | 5 | function getProxyConfiguration({ 6 | jssConfig, 7 | isDevEnv, 8 | layoutServiceRouteResolver, 9 | doNotProxyPrefixesList = [], 10 | shouldDirectProxyPrefixList = [], 11 | }) { 12 | // These are _prefixes_ for routes that should not be proxied at all. 13 | const doNotProxyList = ['/static', '/fonts'].concat(doNotProxyPrefixesList); 14 | 15 | // These are _prefixes_ for routes that should be directly proxied to Sitecore. 16 | const shouldDirectProxyList = [ 17 | '/sitecore/api', 18 | '/api', 19 | '/-/jssmedia', 20 | '/-/media', 21 | '/_/media', 22 | '/_/jssmedia', 23 | '/layouts/system', 24 | ].concat(shouldDirectProxyPrefixList); 25 | 26 | const proxyConfiguration = { 27 | // JSS configuration 28 | jssConfig, 29 | doNotProxyResolver: getDoNotProxyResolver(doNotProxyList), 30 | shouldDirectProxyResolver: getShouldProxyResolver(shouldDirectProxyList), 31 | 32 | // The `layoutServiceRouteResolver` function attempts to resolve a matching app route for the current request. 33 | // The match should return an object in the format: { params: { sitecoreRoute, language } } 34 | // Where `sitecoreRoute` is the value that will be used for the `item` parameter in a Layout Service request, 35 | // and `language` is the value that will be used for the `sc_lang` parameter in a Layout Service request. 36 | layoutServiceRouteResolver, 37 | 38 | options: { 39 | modifyCookies: getModifyCookies(isDevEnv), 40 | modifyLayoutServiceData: getModifyLayoutServiceData(), 41 | handleProxyRedirect: getHandleProxyRedirect(jssConfig), 42 | }, 43 | }; 44 | 45 | return proxyConfiguration; 46 | } 47 | 48 | function getDoNotProxyResolver(doNotProxyList) { 49 | // This function determines whether the proxy middleware should handle the request or not. 50 | // This function is executed before any other actions are performed so the request can 51 | // fall through to the next middleware as soon as possible. 52 | return (req) => { 53 | if (req.method !== 'GET' && req.method !== 'POST') { 54 | return true; 55 | } 56 | const compareUrl = req.url.toLowerCase(); 57 | const doNotProxy = doNotProxyList.some((prefix) => compareUrl.startsWith(prefix)); 58 | return doNotProxy; 59 | }; 60 | } 61 | 62 | function getShouldProxyResolver(shouldDirectProxyList) { 63 | // This function determines whether the proxy middleware should directly proxy the request 64 | // to the Sitecore host. Otherwise, it is assumed that the request should be transformed/rewritten 65 | // into a Layout Service request. This allows the proxy middleware to also proxy non-LayoutService 66 | // requests for GraphQL, Sitecore media library, and other Sitecore APIs. 67 | // This function is executed after `doNotProxyResolver` and before `layoutServiceRouteResolver`. 68 | return (req) => { 69 | const compareUrl = req.url.toLowerCase(); 70 | return shouldDirectProxyList.some((prefix) => compareUrl.startsWith(prefix)); 71 | }; 72 | } 73 | 74 | function getModifyCookies(isDevEnv) { 75 | // This function allows you to modify the cookies that will be sent in the outgoing response. 76 | // `cookies` is an array of cookie objects, e.g. { name: '', value: '', domain: '', httpOnly: '', ...otherCookieProperties } 77 | // The `cookies` array and elements should be modified in place, no return value is 78 | // expected from this method. 79 | return (cookies, outgoingServerResponse, proxyResponse, proxyOptions) => { 80 | cookies.forEach((cookie) => { 81 | if (cookie.name === '.AspNet.Cookies') { 82 | if (isDevEnv) { 83 | // In development, we are making requests from an insecure origin, e.g. http://jss.local.dev 84 | // The `.AspNet.Cookies` cookie, however, is flagged as `Secure`, so it won't be set 85 | // for an insecure origin. 86 | // Therefore, when in development mode, we remove the `Secure` property from the cookie. 87 | cookie.secure = false; 88 | } 89 | // We may want to share the .AspNet.Cookies cookie across domains, so we 90 | // set the `domain` value. 91 | // NOTE: this should probably be set on the Sitecore server intead. 92 | // cookie.domain = '.mydomain.com'; 93 | } 94 | }); 95 | }; 96 | } 97 | 98 | function getModifyLayoutServiceData() { 99 | // This function allows you to modify the Layout Service data received from a proxied layout 100 | // service request. 101 | // The `layoutServiceData` argument should be modified in place, no return value is expected 102 | // from this method. 103 | // IMPORTANT: Be mindful of what you do in this method. Modifying a large JSON object could 104 | // be expensive in terms of performance. 105 | // return ( 106 | // layoutServiceData, 107 | // incomingRequest, 108 | // outgoingServerResponse, 109 | // proxyResponse, 110 | // proxyOptions 111 | // ) => {}; 112 | return undefined; 113 | } 114 | 115 | function getHandleProxyRedirect(jssConfig) { 116 | // This function allows you to handle redirects received from a proxy response. 117 | // return (incomingRequest, outgoingServerResponse, proxyResponse, proxyOptions) => { 118 | // // IMPORTANT: you must pipe or end the response in this method. 119 | // outgoingServerResponse.end(); 120 | // }; 121 | return undefined; 122 | } 123 | -------------------------------------------------------------------------------- /src/modules/jss/sitecore-proxy/initialize.js: -------------------------------------------------------------------------------- 1 | // NOTE: for some reason, using ES module syntax (import/export) didn't work for 2 | // this file and subsequent imported files. Had to use CommonJS module syntax 3 | // to prevent "invalid syntax" errors on Nuxt startup. Oddly, all other modules 4 | // seem to work fine with ESM syntax. It wasn't worth investigating further. 5 | 6 | const { getProxyMiddleware } = require('./nuxt-jss-proxy-middleware'); 7 | const { getConfig: getJssConfigSitecoreProxy } = require('../../../temp/jss-config-sitecore-proxy'); 8 | 9 | module.exports = initialize; 10 | 11 | function initialize(moduleOptions) { 12 | if (!moduleOptions.enabled) { 13 | return; 14 | } 15 | 16 | // In Nuxt modules, `moduleOptions` is the object passed to the module via 17 | // the module declaration in `nuxt.config.js` file. 18 | const isDevEnv = moduleOptions.isDevEnv; 19 | 20 | // In Nuxt modules, Nuxt config (nuxt.config.js) is available via `this.options`. 21 | const nuxtConfig = this.options; 22 | 23 | const middleware = getProxyMiddleware({ 24 | jssConfig: getJssConfigSitecoreProxy(), 25 | isDevEnv, 26 | nuxtConfig, 27 | }); 28 | 29 | this.addServerMiddleware(middleware); 30 | } 31 | -------------------------------------------------------------------------------- /src/modules/jss/sitecore-proxy/nuxt-jss-proxy-middleware.js: -------------------------------------------------------------------------------- 1 | const Router = require('vue-router'); 2 | const nodePath = require('path'); 3 | 4 | const { createSitecoreProxyMiddleware } = require('./create-sitecore-proxy-middleware'); 5 | const { getProxyConfiguration } = require('./default-proxy-configuration'); 6 | 7 | module.exports = { 8 | getProxyMiddleware, 9 | }; 10 | 11 | function getProxyMiddleware({ jssConfig, isDevEnv, nuxtConfig }) { 12 | // Obtain a list of the configured Sitecore routes for the app. 13 | // These route patterns are currently defined in nuxt.config.js via the `router.extendRoutes` property. 14 | const routes = []; 15 | nuxtConfig.router.extendRoutes(routes, nodePath.resolve); 16 | const router = new Router({ 17 | mode: 'abstract', 18 | routes, 19 | }); 20 | 21 | const layoutServiceRouteResolver = (req) => { 22 | const match = router.match(req.url); 23 | const defaultRoute = { 24 | params: { 25 | sitecoreRoute: '/', 26 | }, 27 | }; 28 | 29 | if (!match || !match.params) { 30 | return defaultRoute; 31 | } 32 | 33 | if (!match.params.sitecoreRoute) { 34 | match.params.sitecoreRoute = defaultRoute.params.sitecoreRoute; 35 | } 36 | 37 | return match; 38 | }; 39 | 40 | const proxyConfig = getProxyConfiguration({ 41 | jssConfig, 42 | isDevEnv, 43 | layoutServiceRouteResolver, 44 | doNotProxyPrefixesList: ['/_nuxt', '/__webpack_hmr', '/_loading/sse'], 45 | }); 46 | 47 | const proxyMiddleware = createSitecoreProxyMiddleware(proxyConfig); 48 | 49 | return proxyMiddleware; 50 | } 51 | -------------------------------------------------------------------------------- /src/modules/jss/standard/configure-router.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | configureRouter, 3 | }; 4 | 5 | function configureRouter(nuxtConfig) { 6 | const originalExtendRoutes = nuxtConfig.router.extendRoutes; 7 | nuxtConfig.router.extendRoutes = (routes, resolve) => { 8 | // If there is an existing `extendRoutes` config customization, allow it to run. 9 | if (typeof originalExtendRoutes === 'function') { 10 | originalExtendRoutes(routes, resolve); 11 | } 12 | 13 | // We want JSS route patterns to be first in the `routes` array, so we use `unshift` to 14 | // insert them at the beginning of the array. 15 | // We also want the JSS route patterns inserted by most "complex" pattern to least "complex", 16 | // so we order them in reverse in the `routePatterns` array, so that most complex pattern 17 | // will end up being first in the routes array. 18 | const routePatterns = [ 19 | '/:sitecoreRoute*', 20 | '/:lang([a-z]{2})/:sitecoreRoute*', 21 | '/:lang([a-z]{2}-[A-Z]{2})/:sitecoreRoute*', 22 | ]; 23 | routePatterns.forEach((routePattern) => { 24 | routes.unshift({ 25 | path: routePattern, 26 | components: { 27 | default: resolve(__dirname, '../../../pages/_.vue'), 28 | }, 29 | }); 30 | }); 31 | }; 32 | } 33 | -------------------------------------------------------------------------------- /src/modules/jss/standard/initialize.js: -------------------------------------------------------------------------------- 1 | const nodePath = require('path'); 2 | const { configureRouter } = require('./configure-router'); 3 | 4 | module.exports = initialize; 5 | 6 | function initialize({ dataFetcherType } = {}) { 7 | if (!dataFetcherType) { 8 | throw new Error('JSS Standard module must be initialized with a `dataFetcherType` specified.'); 9 | } 10 | // Add JSS data-fetcher module 11 | this.addModule(['~/modules/jss/data-fetcher/initialize', { dataFetcherType }]); 12 | 13 | // Add JSS Placeholder plugin 14 | this.addPlugin(nodePath.resolve(__dirname, 'sitecore-jss-placeholder-plugin.js')); 15 | 16 | // Add JSS Config plugin 17 | this.addPlugin(nodePath.resolve(__dirname, 'sitecore-jss-config-runtime-plugin.js')); 18 | 19 | const nuxtConfig = this.options; 20 | 21 | // Configure JSS router customizations. 22 | configureRouter(nuxtConfig); 23 | 24 | const nuxtApp = this.nuxt; 25 | 26 | // IMPORTANT: Other JSS plugins may be dependent on the data fetcher and/or config plugins, 27 | // so we need to ensure that those plugins are installed before any other 28 | // JSS plugins. Unfortunately, Nuxt modules do not "push" plugins into the plugins 29 | // array. Instead, when calling `this.addPlugin` from a module, a plugin is added 30 | // to the "front" of the array, i.e. `unshift`. 31 | // The current Nuxt-recommended approach for guaranteeing plugin install order is 32 | // to use the `extendPlugins` hook or `extendPlugins` config option: 33 | // https://nuxtjs.org/api/configuration-extend-plugins 34 | nuxtApp.hook('builder:extendPlugins', (plugins) => { 35 | const dataFetcherPluginIndex = plugins.findIndex( 36 | (plugin) => plugin.src.indexOf('data-fetcher-plugin') !== -1 37 | ); 38 | 39 | if (dataFetcherPluginIndex !== -1) { 40 | const dataFetcherPlugin = plugins[dataFetcherPluginIndex]; 41 | plugins.splice(dataFetcherPluginIndex, 1); 42 | plugins.unshift(dataFetcherPlugin); 43 | } 44 | 45 | const configPluginIndex = plugins.findIndex( 46 | (plugin) => plugin.src.indexOf('jss-config-runtime-plugin') !== -1 47 | ); 48 | 49 | if (configPluginIndex !== -1) { 50 | const configPlugin = plugins[configPluginIndex]; 51 | plugins.splice(configPluginIndex, 1); 52 | plugins.unshift(configPlugin); 53 | } 54 | }); 55 | } 56 | -------------------------------------------------------------------------------- /src/modules/jss/standard/sitecore-jss-config-runtime-plugin.js: -------------------------------------------------------------------------------- 1 | import { getConfig } from '~/temp/config'; 2 | 3 | import Vue from 'vue'; 4 | 5 | export default function initialize(nuxtContext) { 6 | // Define the plugin. 7 | const plugin = { 8 | install: (VueInstance) => 9 | install({ 10 | VueInstance, 11 | nuxtContext, 12 | }), 13 | key: 'SitecoreJssConfigRuntimePlugin', 14 | }; 15 | 16 | // Note: we don't use the built-in Nuxt plugin "inject" functionality because it 17 | // will assign the plugin to the Vue instance using the `key` property. However, we 18 | // want all JSS-related plugins to be accessible via the `$jss` property, so we 19 | // need to use the Vue-provided `Vue.use()` syntax to install JSS plugins. 20 | Vue.use(plugin); 21 | } 22 | 23 | function install({ VueInstance, nuxtContext }) { 24 | if (nuxtContext.$jss && nuxtContext.$jss.getRuntimeConfig) { 25 | console.log('JSS Runtime Config plugin already installed.', nuxtContext.$jss.getRuntimeConfig); 26 | return; 27 | } 28 | 29 | // Define properties that will be available in the `$jss` object. 30 | // Note: these properties are _not_ intended to be reactive, which is why they are 31 | // all functions - to help make it clear to devs that the properties are not reactive. 32 | const pluginProps = { 33 | getRuntimeConfig: getConfig, 34 | }; 35 | 36 | // Who knows what other JSS plugins are doing? So be kind and merge our plugin props 37 | // with any existing `$jss` object properties. 38 | nuxtContext.$jss = Object.assign(nuxtContext.$jss || {}, pluginProps); 39 | nuxtContext.app.$jss = Object.assign(nuxtContext.app.$jss || {}, pluginProps); 40 | VueInstance.prototype.$jss = Object.assign(VueInstance.prototype.$jss || {}, pluginProps); 41 | } 42 | -------------------------------------------------------------------------------- /src/modules/jss/standard/sitecore-jss-placeholder-plugin.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import { SitecoreJssPlaceholderPlugin } from '@sitecore-jss/sitecore-jss-vue'; 3 | import componentFactory from '~/temp/componentFactory'; 4 | 5 | export default function initialize(context) { 6 | Vue.use(SitecoreJssPlaceholderPlugin, { componentFactory }); 7 | } 8 | -------------------------------------------------------------------------------- /src/modules/jss/tracking-api/initialize.js: -------------------------------------------------------------------------------- 1 | const nodePath = require('path'); 2 | 3 | module.exports = initialize; 4 | 5 | function initialize(moduleOptions) { 6 | if (!moduleOptions.enabled) { 7 | return; 8 | } 9 | 10 | this.addPlugin(nodePath.resolve(__dirname, 'sitecore-jss-tracking-api-plugin.js')); 11 | } 12 | -------------------------------------------------------------------------------- /src/modules/jss/tracking-api/sitecore-jss-tracking-api-plugin.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import { trackingApi } from '@sitecore-jss/sitecore-jss-tracking'; 3 | 4 | export default function initialize(nuxtContext) { 5 | if (!nuxtContext.$jss || !nuxtContext.$jss.dataFetcher) { 6 | throw new Error( 7 | 'Unable to initialize the JSS Tracking API plugin. No `dataFetcher` instance was available. Ensure that a JSS `dataFetcher` module or plugin has been installed and initialized.' 8 | ); 9 | } 10 | if (!nuxtContext.$jss || !nuxtContext.$jss.getRuntimeConfig) { 11 | throw new Error( 12 | 'Unable to initialize the JSS Tracking API plugin. No `getRuntimeConfig` method was available. Ensure that a JSS `getRuntimeConfig` plugin has been installed and initialized.' 13 | ); 14 | } 15 | 16 | const jssConfig = nuxtContext.$jss.getRuntimeConfig(); 17 | 18 | // Vue plugins must export a function named 'install' 19 | function install(VueInstance) { 20 | if (nuxtContext.$jss && nuxtContext.$jss.trackingApi) { 21 | console.log('JSS Tracking API plugin already installed.', nuxtContext.$jss.trackingApi); 22 | return; 23 | } 24 | 25 | VueInstance.prototype.$jss = { 26 | // there may be other JSS plugins installed, merge existing properties 27 | ...VueInstance.prototype.$jss, 28 | trackingApi: createTrackingApiClient(jssConfig, nuxtContext.$jss.dataFetcher), 29 | }; 30 | } 31 | 32 | const plugin = { install, key: 'SitecoreJssTrackingApiPlugin' }; 33 | // Note: we don't use the built-in Nuxt plugin "inject" functionality because it 34 | // will assign the plugin to the Vue instance using the `key` property. However, we 35 | // want all JSS-related plugins to be accessible via the `$jss` property, so we 36 | // need to use the Vue-provided `Vue.use()` syntax to install JSS plugins. 37 | Vue.use(plugin); 38 | } 39 | 40 | function createTrackingApiClient(jssConfig, dataFetcher) { 41 | const trackingApiOptions = { 42 | host: jssConfig.sitecoreApiHost, 43 | querystringParams: { 44 | sc_apikey: jssConfig.sitecoreApiKey, 45 | sc_site: jssConfig.sitecoreSiteName, 46 | }, 47 | fetcher: dataFetcher, 48 | }; 49 | 50 | const abandonOptions = { 51 | action: 'flush', 52 | ...trackingApiOptions, 53 | }; 54 | 55 | return { 56 | trackEvent: (...args) => trackingApi.trackEvent(...args, trackingApiOptions), 57 | abandon: () => trackingApi.trackEvent([], abandonOptions), 58 | }; 59 | } 60 | -------------------------------------------------------------------------------- /src/modules/uniform/disconnected-export/initialize.js: -------------------------------------------------------------------------------- 1 | const nodePath = require('path'); 2 | const { ManifestManager } = require('@sitecore-jss/sitecore-jss-dev-tools'); 3 | const { getConfig } = require('../../../temp/config'); 4 | 5 | module.exports = initialize; 6 | 7 | function initialize(moduleOptions) { 8 | if (!moduleOptions.enabled) { 9 | return; 10 | } 11 | 12 | const jssConfig = getConfig(); 13 | 14 | this.options.generate.routes = async () => { 15 | try { 16 | const manifestManager = new ManifestManager({ 17 | appName: jssConfig.jssAppName, 18 | rootPath: nodePath.join(__dirname, '..'), 19 | }); 20 | 21 | const manifestPathMap = []; 22 | // eslint-disable-next-line no-inner-declarations 23 | function generateManifestPathMap(route, parentPath, params, depth = 0) { 24 | // first/initial route should resolve to `/` instead of a named route. 25 | // i.e. we don't want `/home`, we just want `/` for the first/initial route. 26 | const routeName = depth === 0 ? '' : route.name; 27 | const routePath = `${urlJoin(parentPath, routeName).toLowerCase()}`; 28 | 29 | manifestPathMap.push(routePath); 30 | 31 | // traverse the route tree 32 | if (route.children) { 33 | route.children.forEach((child) => { 34 | generateManifestPathMap(child, routePath, params, (depth += 1)); 35 | }); 36 | } 37 | } 38 | 39 | const languages = jssConfig.appLanguages; 40 | // If the app has more than one language, we need to generate a path map for each language so 41 | // we can prefix routes with a language parameter. We also need to modify the nonManifestPathMap 42 | // routes to add language parameter. 43 | if (Array.isArray(languages) && languages.length > 1) { 44 | for (const language of languages) { 45 | const manifest = await manifestManager.getManifest(language); 46 | // Prefix manifest routes with language name 47 | generateManifestPathMap(manifest.items.routes[0], `/${language}`); 48 | } 49 | } else { 50 | const language = jssConfig.defaultLanguage; 51 | const manifest = await manifestManager.getManifest(language); 52 | generateManifestPathMap(manifest.items.routes[0], `/`); 53 | } 54 | 55 | return manifestPathMap; 56 | } catch (error) { 57 | console.error(error); 58 | return []; 59 | } 60 | }; 61 | } 62 | 63 | function urlJoin(...parts) { 64 | // Trim each part to remove slashes (leading or trailing), then join the parts using a slash. 65 | const joinedParts = parts.map((part) => trim(part, '/')).join('/'); 66 | // Trim any extraneous slashes from the joined parts, then prefix the result with a slash 67 | // to ensure a leading slash. 68 | const url = `/${trim(joinedParts, '/')}`; 69 | return url; 70 | } 71 | 72 | function trim(str, char) { 73 | function getSliceStartIndex(str1) { 74 | let startCharIndex = -1; 75 | while (str1.charAt(++startCharIndex) === char); 76 | return startCharIndex; 77 | } 78 | 79 | function getSliceEndIndex(str1) { 80 | let endCharIndex = str1.length; 81 | while (str1.charAt(--endCharIndex) === char); 82 | return endCharIndex + 1; 83 | } 84 | return str.slice(getSliceStartIndex(str), getSliceEndIndex(str)); 85 | } 86 | -------------------------------------------------------------------------------- /src/modules/uniform/services/initialize.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const { 3 | attachUniformServicesToServer, 4 | parseUniformServerConfig, 5 | } = require('@uniformdev/common-server'); 6 | const { 7 | NuxtBuildAndExportEngine, 8 | config: getUniformNuxtConfig, 9 | } = require('@uniformdev/nuxt-server'); 10 | const { createPublishProvider } = require('@uniformdev/publishing-all'); 11 | 12 | // Import a logger 13 | const { consoleLogger } = require('./logging/consoleLogger'); 14 | 15 | module.exports = initialize; 16 | 17 | function initialize(moduleOptions) { 18 | if (!moduleOptions.enabled) { 19 | return; 20 | } 21 | 22 | // In Nuxt modules, the current Nuxt instance is available via `this.nuxt`. 23 | const nuxtApp = this.nuxt; 24 | 25 | extendNuxtConfig(this.options); 26 | setupMiddleware(nuxtApp, moduleOptions); 27 | } 28 | 29 | function extendNuxtConfig(nuxtConfig) { 30 | const uniformNuxtConfig = getUniformNuxtConfig(consoleLogger); 31 | 32 | nuxtConfig.env = { 33 | ...nuxtConfig.env, 34 | ...uniformNuxtConfig.env, 35 | }; 36 | 37 | nuxtConfig.generate = { 38 | ...nuxtConfig.generate, 39 | routes: uniformNuxtConfig.generate.routes, 40 | }; 41 | } 42 | 43 | function setupMiddleware(nuxtApp, moduleOptions) { 44 | // We use the `render:setupMiddleware` Nuxt hook instead of `this.addMiddleware` because using 45 | // `this.addMiddleware` results in a `server.use(middleware)` call. However, for Uniform services, 46 | // we only want to register middleware for specific request methods and paths. 47 | // NOTE: the `render:setupMiddleware` hook is fired before any other middleware are registered. 48 | nuxtApp.hook('render:setupMiddleware', async (server) => { 49 | // Setup Uniform config and attach Uniform-specific middleware to the existing server. 50 | const uniformServerConfig = resolveUniformServerConfig(nuxtApp); 51 | 52 | const buildAndExportEngine = new NuxtBuildAndExportEngine(uniformServerConfig); 53 | 54 | const publishProviderOptions = { 55 | config: uniformServerConfig, 56 | logger: consoleLogger, 57 | }; 58 | 59 | const options = { 60 | uniformServerConfig, 61 | publishProvider: createPublishProvider(publishProviderOptions), 62 | createPublishProvider: createPublishProvider 63 | }; 64 | 65 | attachUniformServicesToServer( 66 | server, 67 | buildAndExportEngine, 68 | moduleOptions.logger || consoleLogger, 69 | options 70 | ); 71 | }); 72 | } 73 | 74 | function resolveUniformServerConfig(nuxtApp) { 75 | let uniformServerConfig; 76 | 77 | // Attempt to resolve a path to `src/uniform.config` 78 | const uniformConfigPath = nuxtApp.resolver.resolveAlias('~/uniform.config.js'); 79 | 80 | // If `uniform.config.js` file exists, get config from there. 81 | if (fs.existsSync(uniformConfigPath)) { 82 | const uniformConfig = require(uniformConfigPath); 83 | // If the config module exports a `getUniformServerConfig` function, call it. 84 | if (typeof uniformConfig.getUniformServerConfig === 'function') { 85 | uniformServerConfig = uniformConfig.getUniformServerConfig(); 86 | } else { 87 | // Otherwise, assume the config module exports a config object. 88 | uniformServerConfig = uniformConfig; 89 | } 90 | } else { 91 | // Otherwise, use `parseUniformServerConfig` method from Uniform library to 92 | // resolve config from environment variables. 93 | uniformServerConfig = parseUniformServerConfig(process.env, consoleLogger, false); 94 | } 95 | 96 | return uniformServerConfig; 97 | } 98 | -------------------------------------------------------------------------------- /src/modules/uniform/services/logging/consoleLogger.js: -------------------------------------------------------------------------------- 1 | const log = require('loglevel'); 2 | 3 | // Be sure to set a level in order for loglevel to bind to the console properly. 4 | // Otherwise, the exported logger instance will have `noop` functions for all 5 | // console methods. 6 | // Likely an issue due to how loglevel exports itself. 7 | 8 | // NOTE: UNIFORM_OPTIONS_DEBUG is exposed via WebpackDefinePlugin. 9 | const level = process.env.UNIFORM_OPTIONS_DEBUG === '1' ? 'debug' : 'info'; 10 | log.setLevel(level); 11 | 12 | exports.consoleLogger = log; 13 | -------------------------------------------------------------------------------- /src/nuxt.config.js: -------------------------------------------------------------------------------- 1 | const serverConfig = require('./server/server.config'); 2 | 3 | const nuxtConfig = { 4 | mode: 'universal', 5 | /* 6 | ** Headers of the page 7 | */ 8 | head: { 9 | meta: [ 10 | { charset: 'utf-8' }, 11 | { name: 'viewport', content: 'width=device-width, initial-scale=1' }, 12 | ], 13 | link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }], 14 | }, 15 | /* 16 | ** Customize the progress-bar color 17 | */ 18 | loading: false, 19 | /* 20 | ** Global CSS 21 | */ 22 | css: [], 23 | /* 24 | ** Plugins to load before mounting the App 25 | */ 26 | plugins: [{ src: '~/plugins/export-route-data-context-plugin', mode: 'server' }], 27 | /* 28 | ** Nuxt.js dev-modules 29 | */ 30 | buildModules: [ 31 | // Doc: https://github.com/nuxt-community/nuxt-tailwindcss 32 | '@nuxtjs/tailwindcss', 33 | ], 34 | /* 35 | ** Nuxt.js modules 36 | */ 37 | modules: [ 38 | // IMPORTANT: the Express module should be first so that other modules can 39 | // attach Express middleware (instead of Connect middleware). 40 | '~/modules/express/initialize', 41 | // IMPORTANT: the JSS Standard module should be loaded before other JSS modules. 42 | // Some (not all) JSS modules may depend on the Standard modules or plugins being 43 | // installed / initialized. e.g. config, dataFetcher 44 | ['~/modules/jss/standard/initialize', { dataFetcherType: 'axios' }], 45 | // JSS i18n module 46 | ['~/modules/jss/i18n/module', { enabled: true }], 47 | // JSS Disconnected Mode module 48 | [ 49 | '~/modules/jss/disconnected-mode/initialize', 50 | { enabled: process.env.JSS_MODE === 'disconnected' }, 51 | ], 52 | // JSS Rendering Host module 53 | [ 54 | '~/modules/jss/rendering-host/initialize', 55 | { 56 | enabled: true, 57 | resolveRenderingHostPublicUrl: (nuxtApp, nuxtCfg) => { 58 | const serverUrl = serverConfig.resolveServerUrl(); 59 | const publicServerUrl = serverConfig.resolvePublicServerUrl(); 60 | return publicServerUrl.url || serverUrl.url; 61 | }, 62 | }, 63 | ], 64 | // JSS Tracking API module 65 | ['~/modules/jss/tracking-api/initialize', { enabled: true }], 66 | // Uniform Disconnected Mode Export module 67 | [ 68 | '~/modules/uniform/disconnected-export/initialize', 69 | { enabled: process.env.JSS_MODE === 'disconnected' }, 70 | ], 71 | // Uniform Standard Services module 72 | ['~/modules/uniform/services/initialize', { enabled: true }], 73 | // Nuxt PWA module 74 | '@nuxtjs/pwa', 75 | // JSS Sitecore Proxy module 76 | // NOTE: the proxy module should likely be installed last (after all other modules) 77 | // because it adds server middleware that may respond to requests that are intended 78 | // to be handled by other middleware / route definitions. In other words, if the proxy 79 | // middleware handles a request, it will likely forward/proxy that request to Sitecore. 80 | [ 81 | '~/modules/jss/sitecore-proxy/initialize', 82 | { 83 | enabled: process.env.JSS_MODE !== 'disconnected', 84 | isDevEnv: process.env.NODE_ENV !== 'production', 85 | }, 86 | ], 87 | ], 88 | /* 89 | ** Build configuration 90 | */ 91 | build: { 92 | /* 93 | ** You can extend webpack config here 94 | */ 95 | extend(config, ctx) { 96 | if (ctx.isDev) { 97 | config.devtool = ctx.isClient ? 'source-map' : 'inline-source-map'; 98 | } 99 | }, 100 | }, 101 | generate: { 102 | dir: 'out', 103 | crawler: false, 104 | }, 105 | server: { 106 | port: serverConfig.resolveListeningPort(), 107 | }, 108 | }; 109 | 110 | module.exports = nuxtConfig; 111 | -------------------------------------------------------------------------------- /src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@uniformdev/uniform-sitecore-jss-nuxtjs-starterkit", 3 | "version": "1.0.0", 4 | "private": true, 5 | "config": { 6 | "appName": "uniform-jss-kit", 7 | "rootPlaceholders": [ 8 | "uniform-jss-kit-content" 9 | ], 10 | "sitecoreConfigPath": "/App_Config/Include/zzz", 11 | "graphQLEndpointPath": "/api/uniform-jss-kit", 12 | "language": "en", 13 | "appLanguages": [ 14 | "en" 15 | ] 16 | }, 17 | "scripts": { 18 | "bootstrap": "node scripts/bootstrap.js", 19 | "deploy": "npm run export && node deploy.js", 20 | "export": "cross-env-shell JSS_MODE=connected NUXT_EXPORT=true \"npm-run-all --serial bootstrap nuxt:generate\"", 21 | "generate-manifest": "jss manifest -c -d", 22 | "start": "cross-env-shell JSS_MODE=connected \"npm-run-all --serial bootstrap --parallel nuxt:dev watch-components\"", 23 | "start:disconnected": "cross-env-shell JSS_MODE=disconnected \"npm-run-all --serial bootstrap --parallel nuxt:dev watch-components\"", 24 | "start:disconnected:withtunnel": "cross-env-shell JSS_MODE=disconnected \"npm-run-all --serial bootstrap --parallel nuxt:start:withtunnel watch-components\"", 25 | "start:production": "cross-env-shell JSS_MODE=connected NODE_ENV=production \"npm-run-all --serial bootstrap nuxt:build nuxt:start", 26 | "start:withtunnel": "cross-env-shell JSS_MODE=connected \"npm-run-all --serial bootstrap --parallel nuxt:start:withtunnel watch-components\"", 27 | "nuxt:dev": "nuxt dev", 28 | "nuxt:build": "nuxt build", 29 | "nuxt:generate": "nuxt generate", 30 | "nuxt:start": "node server/server.js --start", 31 | "nuxt:start:withtunnel": "node server/tunnel.js", 32 | "watch-components": "node scripts/generate-component-factory.js --watch", 33 | "test": "jest" 34 | }, 35 | "dependencies": { 36 | "@nuxtjs/pwa": "^3.3.5", 37 | "@sitecore-jss/sitecore-jss-tracking": "~13.0.0", 38 | "@sitecore-jss/sitecore-jss-vue": "~13.0.0", 39 | "@uniformdev/nuxt-server": "4.0.210223-3", 40 | "@uniformdev/publishing-all": "4.0.210223-3", 41 | "axios": "^0.19.2", 42 | "body-parser": "^1.19.0", 43 | "compression": "^1.7.4", 44 | "cors": "^2.8.5", 45 | "express": "^4.16.4", 46 | "follow-redirects": "^1.10.0", 47 | "loglevel": "^1.6.7", 48 | "nuxt": "^2.15.8", 49 | "set-cookie-parser": "^2.4.3", 50 | "vue-loading-overlay": "^3.3.2" 51 | }, 52 | "devDependencies": { 53 | "@babel/register": "^7.14.5", 54 | "@nuxtjs/tailwindcss": "^4.1.3", 55 | "@sitecore-jss/sitecore-jss-cli": "^13.0.0", 56 | "@sitecore-jss/sitecore-jss-dev-tools": "^13.0.0", 57 | "@sitecore-jss/sitecore-jss-manifest": "^13.0.0", 58 | "@vue/test-utils": "^1.0.3", 59 | "babel-eslint": "^10.1.0", 60 | "babel-jest": "^26.1.0", 61 | "chokidar": "^3.2.0", 62 | "cross-env": "^7.0.2", 63 | "dotenv": "^8.2.0", 64 | "jest": "^26.1.0", 65 | "ngrok": "^4.0.1", 66 | "nodemon": "^2.0.2", 67 | "npm-run-all": "^4.1.5", 68 | "postcss": "^8.3.3", 69 | "serve-static": "^1.14.1", 70 | "tailwindcss": "^2.1.4", 71 | "typescript": "^4.3.2", 72 | "vue-jest": "^4.0.0-beta.3", 73 | "webpack-require-from": "^1.8.1" 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/pages/README.md: -------------------------------------------------------------------------------- 1 | # PAGES 2 | 3 | This directory contains your Application Views and Routes. 4 | The framework reads all the `*.vue` files inside this directory and creates the router of your application. 5 | 6 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/routing). 7 | -------------------------------------------------------------------------------- /src/pages/_.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 18 | 19 | 112 | 113 | 118 | 156 | -------------------------------------------------------------------------------- /src/plugins/export-route-data-context-plugin.js: -------------------------------------------------------------------------------- 1 | /* 2 | During Next export, we need to export Layout Service data to static JSON files that are emitted to the 3 | same output directory as the static site export artifacts. 4 | 5 | The easiest place to do this is within the `getRouteData` function used in the 6 | `src/lib/layoutServiceUtils.js` file. However, we don't want to pollute that file with a bunch of 7 | export-specific code, so use this module to provide any export-specific functionality. 8 | */ 9 | 10 | import fs from 'fs'; 11 | import nodePath from 'path'; 12 | 13 | export default (pluginContext) => { 14 | const { app } = pluginContext; 15 | app.getExportRouteDataContext = () => { 16 | const exportOutDir = resolveExportOutDir(); 17 | 18 | function exportRouteDataWriter(routePath, language, data) { 19 | let routeName = routePath === '/' ? 'home' : routePath; 20 | if (routeName.startsWith('/')) { 21 | routeName = routeName.substring(1); 22 | } 23 | 24 | // NOTE: routeName may contain nested paths, e.g. /news-events/press-releases/release-01 25 | // Need to ensure that we create the folder structure to match. 26 | const dataFolder = `${exportOutDir}/data/${routeName}`; 27 | const filePath = `${dataFolder}/${language}.json`; 28 | 29 | return ensureDirectoryExists(dataFolder).then(() => { 30 | return writeFile(filePath, data); 31 | }); 32 | } 33 | 34 | return { 35 | exportRouteDataWriter, 36 | exportOutDir, 37 | }; 38 | }; 39 | }; 40 | 41 | function resolveExportOutDir() { 42 | // Unfortunately there isn't a great way to obtain the resolved `nuxtConfig.generate.out` value that 43 | // Nuxt uses for exporting at runtime. 44 | // So, we ask that if devs use a custom `nuxt.config.generate.out` property for `nuxt generate` that they 45 | // also set an environment variable that we can use when writing export data. 46 | // Our default value for `nuxt.config.generate.out` is set to the `/out` folder in the src root, 47 | // so we use that as our default here as well. 48 | const exportOutDir = process.env.EXPORT_OUT_DIR || nodePath.resolve(process.cwd(), 'out'); 49 | return exportOutDir; 50 | } 51 | 52 | function writeFile(filePath, data) { 53 | return new Promise((resolve, reject) => { 54 | fs.writeFile(filePath, JSON.stringify(data, null, 2), { encoding: 'utf-8' }, (err) => { 55 | if (err) { 56 | reject(err); 57 | } else { 58 | resolve(); 59 | } 60 | }); 61 | }); 62 | } 63 | 64 | function ensureDirectoryExists(dirPath) { 65 | return new Promise((resolve, reject) => { 66 | if (fs.existsSync(dirPath)) { 67 | resolve(); 68 | return; 69 | } 70 | 71 | // The `recursive` option ensures that nested paths are fully created. 72 | // For example, `/out/data/boutiques-restaurants/agatha` would create 73 | // the following folder structure: 74 | // out 75 | // data 76 | // boutiques-restaurants 77 | // agatha 78 | fs.mkdir(dirPath, { recursive: true }, (err) => { 79 | if (err) { 80 | reject(err); 81 | } else { 82 | resolve(); 83 | } 84 | }); 85 | }); 86 | } 87 | -------------------------------------------------------------------------------- /src/scripts/bootstrap.js: -------------------------------------------------------------------------------- 1 | const { 2 | generateRuntimeConfig, 3 | generateRenderingHostConfig, 4 | generateSitecoreProxyConfig, 5 | } = require('./generate-config'); 6 | const { getUniformConfig } = require('../uniform.config'); 7 | /* 8 | BOOTSTRAPPING 9 | The bootstrap process runs before build, and generates JS that needs to be 10 | included into the build - specifically, the component name to component mapping, 11 | and the global config module(s). 12 | */ 13 | 14 | // Resolve execution modes 15 | const disconnected = process.env.JSS_MODE === 'disconnected'; 16 | const isExport = process.env.NUXT_EXPORT === 'true'; 17 | 18 | /* 19 | CONFIG GENERATION 20 | Generates the /src/temp/config.js file which contains runtime configuration 21 | that the app can import and use. 22 | */ 23 | const runtimeConfigOverrides = getRuntimeConfigOverrides(); 24 | generateRuntimeConfig(runtimeConfigOverrides); 25 | 26 | // Rendering Host config generation can be removed / disabled if rendering host is not being used. 27 | generateRenderingHostConfig(); 28 | 29 | // Sitecore Proxy config generation can be removed / disabled if Sitecore proxy is not being used. 30 | generateSitecoreProxyConfig(); 31 | 32 | /* 33 | COMPONENT FACTORY GENERATION 34 | */ 35 | require('./generate-component-factory'); 36 | 37 | function getRuntimeConfigOverrides() { 38 | const configOverride = {}; 39 | 40 | const uniformConfig = getUniformConfig(); 41 | 42 | if (disconnected) { 43 | const port = process.env.PORT || 3000; 44 | configOverride.sitecoreApiHost = `http://localhost:${port}`; 45 | } 46 | 47 | if (isExport) { 48 | const siteName = uniformConfig.UNIFORM_API_SITENAME; 49 | if (siteName) { 50 | configOverride.sitecoreSiteName = siteName; 51 | } 52 | 53 | const apiKey = process.env.UNIFORM_API_KEY; 54 | if (apiKey) { 55 | configOverride.apiKey = apiKey; 56 | } 57 | 58 | const layoutServiceHost = uniformConfig.UNIFORM_API_URL; 59 | if (layoutServiceHost) { 60 | configOverride.sitecoreApiHost = layoutServiceHost; 61 | } 62 | } 63 | 64 | return configOverride; 65 | } 66 | -------------------------------------------------------------------------------- /src/scripts/generate-component-factory.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const chokidar = require('chokidar'); 4 | 5 | /* 6 | COMPONENT FACTORY GENERATION 7 | Generates the /src/temp/componentFactory.js file which maps React components 8 | to JSS components. 9 | 10 | The component factory is a mapping between a string name and a React component instance. 11 | When the Sitecore Layout service returns a layout definition, it returns named components. 12 | This mapping is used to construct the component hierarchy for the layout. 13 | 14 | The default convention uses the parent folder name as the component name, 15 | but it is customizable in generateComponentFactory(). 16 | 17 | NOTE: this script can run in two modes. The default mode, the component factory file is written once. 18 | But if `--watch` is a process argument, the component factory source folder will be watched, 19 | and the componentFactory.js rewritten on added or deleted files. 20 | This is used during `jss start` to pick up new or removed components at runtime. 21 | */ 22 | 23 | /* eslint-disable no-console */ 24 | 25 | const componentFactoryPath = path.resolve('temp/componentFactory.js'); 26 | const componentRootPath = path.resolve(path.join(__dirname, '../components')); 27 | 28 | const isWatch = process.argv.some((arg) => arg === '--watch'); 29 | 30 | if (isWatch) { 31 | watchComponentFactory(); 32 | } else { 33 | writeComponentFactory(); 34 | } 35 | 36 | function watchComponentFactory() { 37 | console.log(`Watching for changes to component factory sources in ${componentRootPath}...`); 38 | 39 | chokidar 40 | .watch(componentRootPath, { ignoreInitial: true, awaitWriteFinish: true }) 41 | .on('add', writeComponentFactory) 42 | .on('unlink', writeComponentFactory); 43 | } 44 | 45 | function writeComponentFactory() { 46 | const componentFactory = generateComponentFactory(); 47 | 48 | console.log(`Writing component factory to ${componentFactoryPath}`); 49 | 50 | fs.writeFileSync(componentFactoryPath, componentFactory, { encoding: 'utf8' }); 51 | } 52 | 53 | function generateComponentFactory() { 54 | // by convention, we expect to find Vue components 55 | // * under /src/components 56 | // * with a .vue extension to define a component file 57 | // If you'd like to use your own convention, encode it below. 58 | // NOTE: generating the component factory is also totally optional, 59 | // and it can be maintained manually if preferred. 60 | 61 | const imports = []; 62 | const registrations = []; 63 | const ignoreComponent = (componentFile) => { 64 | if (componentFile.indexOf('MenuMobile') !== -1 || componentFile.indexOf('MenuDesktop') !== -1) { 65 | return true; 66 | } 67 | return false; 68 | }; 69 | 70 | const componentFiles = extractVueFiles(componentRootPath); 71 | componentFiles.forEach((componentFile) => { 72 | if (!fs.existsSync(componentFile)) { 73 | return; 74 | } 75 | if (ignoreComponent(componentFile)) { 76 | return; 77 | } 78 | 79 | console.debug(`Registering JSS component ${componentFile}`); 80 | const componentName = path.basename(componentFile, '.vue'); 81 | const importVarName = componentName.replace(/[^\w]+/g, ''); 82 | 83 | imports.push( 84 | `import ${importVarName} from '../components/${componentFile 85 | .replace(path.join(componentRootPath, '/'), '') 86 | .replace(/\\/g, '/') 87 | .replace('.vue', '')}';` 88 | ); 89 | 90 | // Create component factory map entries for Sitecore-named renderings/components. 91 | // You can also use this opportunity to provide aliases for component names. 92 | // For instance, if the component name is MyComponent, then register both 93 | // `MyComponent` and `My Component` as map references to the MyComponent component. 94 | // This can be useful when migrated existing components/renderings that may not 95 | // have been create specifically for JSS. 96 | const componentNameAndAliases = [ 97 | componentName, 98 | // componentName.match(/([A-Z][a-z]+)/g).join(' '), 99 | ]; 100 | componentNameAndAliases.forEach((componentName) => { 101 | registrations.push(`components.set('${componentName}', ${importVarName});`); 102 | }); 103 | }); 104 | 105 | return `/* eslint-disable */ 106 | // Do not edit this file, it is auto-generated at build time! 107 | // See scripts/generate-component-factory.js to modify the generation of this file. 108 | ${imports.join('\n')} 109 | 110 | const components = new Map(); 111 | ${registrations.join('\n')} 112 | 113 | export default function componentFactory(componentName) { 114 | return components.get(componentName); 115 | }; 116 | `; 117 | } 118 | 119 | /** 120 | * Recursively iterates the given folderPath, creating a flat array of found .vue file paths. 121 | * For example, given the following folder structure and using `/components` as the root folderPath: 122 | * /components/component0.vue 123 | * /components/subfolder/component1.vue 124 | * 125 | * The output would be: 126 | * ['component0.vue', 'subfolder/component1.vue'] 127 | */ 128 | function extractVueFiles(folderPath) { 129 | let results = []; 130 | fs.readdirSync(folderPath).forEach((pathName) => { 131 | const computedPath = path.join(folderPath, pathName); 132 | const stat = fs.statSync(computedPath); 133 | if (stat && stat.isDirectory()) { 134 | results = results.concat(extractVueFiles(computedPath)); 135 | } else if (path.extname(computedPath).toLowerCase() === '.vue') { 136 | results.push(computedPath); 137 | } 138 | }); 139 | return results; 140 | } 141 | -------------------------------------------------------------------------------- /src/scripts/generate-config.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const packageConfig = require('../package.json'); 4 | 5 | /* eslint-disable no-console */ 6 | 7 | /** 8 | * Generate config 9 | * The object returned from this function will be made available by importing src/temp/config.js. 10 | * This is executed prior to the build running, so it's a way to inject environment or build config-specific 11 | * settings as variables into the JSS app. 12 | * NOTE! Any configs returned here will be written into the client-side JS bundle. DO NOT PUT SECRETS HERE. 13 | * @param {object} configOverrides Keys in this object will override any equivalent global config keys. 14 | */ 15 | module.exports = { 16 | generateRuntimeConfig, 17 | generateRenderingHostConfig, 18 | generateSitecoreProxyConfig, 19 | }; 20 | 21 | function generateRuntimeConfig(configOverrides) { 22 | const standardConfig = resolveStandardConfig(); 23 | 24 | const envConfigKeyMap = { 25 | SITECORE_API_HOST: 'sitecoreApiHost', 26 | SITECORE_API_KEY: 'sitecoreApiKey', 27 | SITECORE_GRAPHQL_ENDPOINT: 'graphQLEndpoint', 28 | SITECORE_GRAPHQL_ENDPOINT_PATH: 'graphQLEndpointPath', 29 | SITECORE_JSS_APP_NAME: 'jssAppName', 30 | SITECORE_JSS_DEFAULT_LANGUAGE: 'defaultLanguage', 31 | SITECORE_SITE_NAME: 'sitecoreSiteName', 32 | }; 33 | 34 | const envConfig = resolveEnvConfig(envConfigKeyMap); 35 | 36 | const config = Object.assign(standardConfig, envConfig, configOverrides); 37 | 38 | const moduleFormats = [ 39 | { exportStatement: 'export ', filename: 'config.js' }, 40 | { exportStatement: 'module.exports = { getConfig };\n', filename: 'config.cjs.js' }, 41 | ]; 42 | moduleFormats.forEach((moduleFormat) => { 43 | const configText = `/* eslint-disable */ 44 | // Do not edit this file, it is auto-generated at build time! 45 | // See scripts/generate-config.js to modify the generation of this file. 46 | ${moduleFormat.exportStatement} function getConfig() { 47 | const config = ${JSON.stringify(config, null, 2)}; 48 | 49 | if (typeof window !== 'undefined' && typeof window.__NUXT__ !== 'undefined' && typeof window.__NUXT__.jssConfig !== 'undefined') { 50 | Object.assign(config, window.__NUXT__.jssConfig); 51 | } 52 | 53 | return config; 54 | }`; 55 | 56 | const configPath = writeConfigFile(configText, moduleFormat.filename); 57 | console.log(`Runtime config written to ${configPath}`); 58 | }); 59 | } 60 | 61 | function generateRenderingHostConfig(configOverrides) { 62 | const standardConfig = resolveStandardConfig(); 63 | 64 | const envConfigKeyMap = { 65 | RENDERING_HOST_SITECORE_API_HOST: 'sitecoreApiHost', 66 | RENDERING_HOST_SITECORE_API_KEY: 'sitecoreApiKey', 67 | RENDERING_HOST_SITECORE_SITE_NAME: 'sitecoreSiteName', 68 | }; 69 | 70 | const envConfig = resolveEnvConfig(envConfigKeyMap); 71 | 72 | const config = Object.assign(standardConfig, envConfig, configOverrides); 73 | 74 | const configText = `/* eslint-disable */ 75 | // Do not edit this file, it is auto-generated at build time! 76 | // See scripts/generate-config.js to modify the generation of this file. 77 | module.exports = { getConfig }; 78 | function getConfig() { 79 | const config = ${JSON.stringify(config, null, 2)}; 80 | return config; 81 | }`; 82 | 83 | const configPath = writeConfigFile(configText, 'jss-config-rendering-host.js'); 84 | 85 | console.log(`Rendering Host config written to ${configPath}`); 86 | } 87 | 88 | function generateSitecoreProxyConfig(configOverrides) { 89 | const standardConfig = resolveStandardConfig(); 90 | 91 | const envConfigKeyMap = { 92 | SITECORE_PROXY_SITECORE_API_HOST: 'sitecoreApiHost', 93 | SITECORE_PROXY_SITECORE_API_KEY: 'sitecoreApiKey', 94 | SITECORE_PROXY_SITECORE_SITE_NAME: 'sitecoreSiteName', 95 | }; 96 | 97 | const envConfig = resolveEnvConfig(envConfigKeyMap); 98 | 99 | const config = Object.assign(standardConfig, envConfig, configOverrides); 100 | 101 | const configText = `/* eslint-disable */ 102 | // Do not edit this file, it is auto-generated at build time! 103 | // See scripts/generate-config.js to modify the generation of this file. 104 | module.exports = { getConfig }; 105 | function getConfig() { 106 | const config = ${JSON.stringify(config, null, 2)}; 107 | return config; 108 | }`; 109 | 110 | const configPath = writeConfigFile(configText, 'jss-config-sitecore-proxy.js'); 111 | 112 | console.log(`Sitecore Proxy config written to ${configPath}`); 113 | } 114 | 115 | function resolveEnvConfig(envConfigKeyMap) { 116 | const envConfig = {}; 117 | Object.keys(envConfigKeyMap).forEach((envConfigKey) => { 118 | if (process.env[envConfigKey]) { 119 | envConfig[envConfigKeyMap[envConfigKey]] = process.env[envConfigKey]; 120 | } 121 | }); 122 | 123 | return envConfig; 124 | } 125 | 126 | function resolveStandardConfig() { 127 | const defaultConfig = { 128 | sitecoreApiKey: 'no-api-key-set', 129 | sitecoreApiHost: '', 130 | jssAppName: 'Unknown', 131 | }; 132 | 133 | // require + combine config sources 134 | const scjssConfig = transformScJssConfig(); 135 | const packageJson = transformPackageConfig(); 136 | 137 | // optional: 138 | // do any other dynamic config source (e.g. environment-specific config files) 139 | // Object.assign merges the objects in order, so the 140 | // package.json config can override the calculated config, 141 | // scjssconfig.json overrides it, 142 | // and finally config passed in the configOverrides param wins. 143 | const config = Object.assign(defaultConfig, packageJson, scjssConfig); 144 | 145 | // The GraphQL endpoint is an example of making a _computed_ config setting 146 | // based on other config settings. 147 | addGraphQLConfig(config); 148 | 149 | return config; 150 | } 151 | 152 | function transformScJssConfig() { 153 | // scjssconfig.json may not exist if you've never run setup 154 | // so if it doesn't we substitute a fake object 155 | let config; 156 | try { 157 | // eslint-disable-next-line global-require 158 | config = require('../scjssconfig.json'); 159 | } catch (e) { 160 | return {}; 161 | } 162 | 163 | if (!config) return {}; 164 | 165 | return { 166 | sitecoreApiKey: config.sitecore.apiKey, 167 | sitecoreApiHost: config.sitecore.layoutServiceHost, 168 | }; 169 | } 170 | 171 | function transformPackageConfig() { 172 | if (!packageConfig.config) { 173 | return {}; 174 | } 175 | 176 | return { 177 | jssAppName: packageConfig.config.appName, 178 | // Typically, app name and site name will be the same, but sometimes they're not. 179 | // And that causes all sorts of unpleasantness. So we allow for separate config values. 180 | sitecoreSiteName: packageConfig.config.sitecoreSiteName || packageConfig.config.appName, 181 | defaultLanguage: packageConfig.config.language || 'en', 182 | graphQLEndpointPath: packageConfig.config.graphQLEndpointPath || null, 183 | appLanguages: packageConfig.config.appLanguages || [packageConfig.config.language || 'en'], 184 | }; 185 | } 186 | 187 | /** 188 | * Adds the GraphQL endpoint URL to the config object, and ensures that components needed to calculate it are valid 189 | */ 190 | function addGraphQLConfig(baseConfig) { 191 | if (!baseConfig.graphQLEndpointPath || typeof baseConfig.sitecoreApiHost === 'undefined') { 192 | console.error( 193 | 'The `graphQLEndpointPath` and/or `layoutServiceHost` configurations were not defined. You may need to run `jss setup`.' 194 | ); 195 | process.exit(1); 196 | } 197 | 198 | // eslint-disable-next-line no-param-reassign 199 | baseConfig.graphQLEndpoint = `${baseConfig.sitecoreApiHost}${baseConfig.graphQLEndpointPath}?sc_apikey=${baseConfig.sitecoreApiKey}`; 200 | } 201 | 202 | function writeConfigFile(configData, filename) { 203 | const configPath = path.resolve(`temp/${filename}`); 204 | 205 | fs.writeFileSync(configPath, configData, { encoding: 'utf8' }); 206 | 207 | return configPath; 208 | } 209 | -------------------------------------------------------------------------------- /src/server/server.config.js: -------------------------------------------------------------------------------- 1 | // Process values provided in `.env` file(s) 2 | const { config: dotenvConfig } = require('dotenv'); 3 | dotenvConfig(); 4 | 5 | module.exports = { 6 | resolveServerUrl, 7 | resolvePublicServerUrl, 8 | resolveListeningPort, 9 | }; 10 | 11 | function resolveListeningPort() { 12 | return process.env.PORT || 3000; 13 | } 14 | 15 | function resolveServerUrl() { 16 | const serverPort = process.env.SERVER_PORT || process.env.PORT || 3000; 17 | const serverHostname = process.env.SERVER_HOST_NAME || 'localhost'; 18 | const serverProtocol = process.env.SERVER_PROTOCOL || 'http'; 19 | 20 | return { 21 | parts: { 22 | port: serverPort, 23 | hostname: serverHostname, 24 | protocol: serverProtocol, 25 | }, 26 | url: `${serverProtocol}://${serverHostname}:${serverPort}`, 27 | }; 28 | } 29 | 30 | function resolvePublicServerUrl() { 31 | const publicPort = process.env.SERVER_PUBLIC_PORT; 32 | const publicHostname = process.env.SERVER_PUBLIC_HOST_NAME; 33 | const publicProtocol = process.env.SERVER_PUBLIC_PROTOCOL; 34 | 35 | return { 36 | parts: { 37 | port: publicPort, 38 | hostname: publicHostname, 39 | protocol: publicProtocol, 40 | }, 41 | // If no publicHostName has been resolved, return undefined for the `url` value 42 | url: publicHostname 43 | ? `${publicProtocol}://${publicHostname}${publicPort ? ':' + publicPort : ''}` 44 | : undefined, 45 | }; 46 | } 47 | -------------------------------------------------------------------------------- /src/server/server.js: -------------------------------------------------------------------------------- 1 | const nuxt = require('nuxt'); 2 | const { showBanner } = require('@nuxt/cli/dist/cli-banner'); 3 | 4 | // Allow starting from the command line or starting from exported `start` method. 5 | if (process.argv.some((arg) => arg === '--start')) { 6 | start(); 7 | } 8 | 9 | module.exports = { 10 | start, 11 | }; 12 | 13 | async function start({ tunnelUrl } = {}) { 14 | const isDev = process.env.NODE_ENV !== 'production'; 15 | 16 | // Init Nuxt.js 17 | const app = await nuxt.loadNuxt(isDev ? 'dev' : 'start'); 18 | 19 | // Build only in dev mode. 20 | // In production mode, the app should be built before starting the server. 21 | if (isDev) { 22 | await nuxt.build(app); 23 | } 24 | 25 | await app.listen(app.options.server.port); 26 | 27 | // If the server was started with a tunnel (e.g. ngrok) pointing to it, display 28 | // the tunnel url in the banner for convenience. 29 | if (tunnelUrl) { 30 | app.options.cli.badgeMessages = [...app.options.cli.badgeMessages, `Tunnel URL: ${tunnelUrl}`]; 31 | } 32 | 33 | // Show the server banner. Bit of a hack to use `showBanner` from the Nuxt CLI package, 34 | // but not really another way to do it via Nuxt programmatically. 35 | showBanner(app); 36 | } 37 | -------------------------------------------------------------------------------- /src/server/tunnel.js: -------------------------------------------------------------------------------- 1 | const ngrok = require('ngrok'); 2 | const { URL } = require('url'); 3 | const serverConfig = require('./server.config'); 4 | 5 | const serverUrl = serverConfig.resolveServerUrl(); 6 | 7 | startTunnel(serverUrl.parts.hostname, { 8 | proto: serverUrl.parts.protocol, 9 | port: serverUrl.parts.port, 10 | }) 11 | .then((tunnelUrl) => { 12 | const parsedTunnelUrl = new URL(tunnelUrl); 13 | process.env.SERVER_PUBLIC_HOST_NAME = parsedTunnelUrl.hostname; 14 | process.env.SERVER_PUBLIC_PORT = parsedTunnelUrl.port; 15 | // node URL.protocol returns the protocol name along with a trailing `:`, we don't need that. 16 | process.env.SERVER_PUBLIC_PROTOCOL = parsedTunnelUrl.protocol.replace(':', ''); 17 | 18 | // start the Express server 19 | require('./server').start({ tunnelUrl }); 20 | }) 21 | .catch((err) => { 22 | console.error(err); 23 | }); 24 | 25 | // This function starts an ngrok tunnel that will expose the express server 26 | // via a public URL, e.g. https://13453.ngrok.io 27 | async function startTunnel(serverHostname, options = { port: 80, proto: 'http', quiet: false }) { 28 | if (!serverHostname) { 29 | throw new Error( 30 | 'Unable to start tunnel as no hostname for the underlying server was specified.' 31 | ); 32 | } 33 | 34 | const rewriteHost = `${serverHostname}:${options.port}`; 35 | const finalOptions = { 36 | ...options, 37 | host_header: 'rewrite', 38 | addr: rewriteHost, 39 | }; 40 | 41 | return ngrok.connect(finalOptions).then((url) => { 42 | if (!options.quiet) { 43 | console.log(`Tunnel started, forwarding '${url}' to '${rewriteHost}'`); 44 | } 45 | return url; 46 | }); 47 | } 48 | -------------------------------------------------------------------------------- /src/sitecore/.gitignore: -------------------------------------------------------------------------------- 1 | manifest 2 | package 3 | update-package 4 | *.deploysecret.config -------------------------------------------------------------------------------- /src/sitecore/config/uniform-jss.config: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 24 | 25 | 26 | 27 | 36 | 65 | 66 | 67 | 68 | 77 | 85 | 86 | 98 | 99 | 100 | 101 | mw=100,mh=50 102 | 103 | 104 | mw=300 105 | mw=100 106 | 107 | 108 | 109 | 110 | 115 | 116 | 117 | 118 | $(url) 119 | 120 | true 121 | true 122 | 123 | 124 | false 125 | false 126 | false 127 | false 128 | true 129 | 130 | 131 | 132 | 133 | 134 | context 135 | 136 | /sitecore/templates/Project/uniform-jss-kit 137 | 138 | 139 | 140 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | -------------------------------------------------------------------------------- /src/sitecore/definitions/components/Hero.sitecore.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-unused-vars 2 | import { CommonFieldTypes, SitecoreIcon, Manifest } from '@sitecore-jss/sitecore-jss-manifest'; 3 | 4 | /** 5 | * Adds the Hero component to the disconnected manifest. 6 | * This function is invoked by convention (*.sitecore.js) when `jss manifest` is run. 7 | * @param {Manifest} manifest Manifest instance to add components to 8 | */ 9 | export default function(manifest) { 10 | manifest.addComponent({ 11 | name: 'Hero', 12 | displayName: 'Hero', 13 | // totally optional, but fun 14 | icon: 'software/16x16/component_yellow.png', 15 | fields: [ 16 | { name: 'title', type: CommonFieldTypes.SingleLineText, section: 'Content' }, 17 | { name: 'subtitle', type: CommonFieldTypes.SingleLineText, section: 'Content' }, 18 | { name: 'text', type: CommonFieldTypes.RichText, section: 'Content' }, 19 | { name: 'image', type: CommonFieldTypes.SingleLineText, section: 'Content' }, 20 | { name: 'primaryCTALink', type: CommonFieldTypes.GeneralLink, section: 'Call to action' }, 21 | { name: 'primaryCTATitle', type: CommonFieldTypes.SingleLineText, section: 'Call to action' }, 22 | { name: 'secondaryCTALink', type: CommonFieldTypes.GeneralLink, section: 'Call to action' }, 23 | { 24 | name: 'secondaryCTATitle', 25 | type: CommonFieldTypes.SingleLineText, 26 | section: 'Call to action', 27 | }, 28 | ], 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /src/sitecore/definitions/config.js: -------------------------------------------------------------------------------- 1 | // this file is imported by default prior to executing the jss manifest command 2 | // use this to enable transpilation or any other pre-manifest configurations that are needed. 3 | 4 | console.log('Enabling Babel 7 transpilation for the manifest...'); 5 | 6 | // register Babel compiler 7 | require('@babel/register')({ 8 | presets: [ 9 | [ 10 | '@babel/preset-env', 11 | { 12 | targets: { 13 | node: 'current', 14 | }, 15 | }, 16 | ], 17 | ], 18 | // override React default Babel config 19 | babelrc: false, 20 | }); 21 | -------------------------------------------------------------------------------- /src/sitecore/definitions/content.sitecore.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | import { Manifest, ItemDefinition } from '@sitecore-jss/sitecore-jss-manifest'; 3 | import { mergeFs, MergeFsResult } from '@sitecore-jss/sitecore-jss-dev-tools'; 4 | import path from 'path'; 5 | import fs from 'fs'; 6 | /* eslint-enable no-unused-vars */ 7 | 8 | /** 9 | * Adds non-route content items to the disconnected manifest. 10 | * Content items are conventionally defined in /data/content, similar to route items. 11 | * This function is invoked by convention (*.sitecore.js) when `jss manifest` is run. 12 | * @param {Manifest} manifest 13 | * @returns {Promise} 14 | */ 15 | export default function addContentToManifest(manifest) { 16 | const rootItemName = 'global'; 17 | const rootItemDisplayName = 'Global Content'; 18 | const startPath = './data/content'; // relative to process invocation (i.e. where package.json lives) 19 | 20 | if (!fs.existsSync(startPath)) return Promise.resolve(); 21 | 22 | return mergeFs(startPath) 23 | .then((result) => { 24 | const items = convertToItems( 25 | result, 26 | path.resolve(startPath), 27 | rootItemName, 28 | rootItemDisplayName, 29 | manifest.language 30 | ); 31 | return items; 32 | }) 33 | .then((contentData) => { 34 | if (contentData) { 35 | manifest.addContent(contentData); 36 | } 37 | }); 38 | } 39 | 40 | /** 41 | * Maps filesystem content data into manifest content item data. 42 | * @param {MergeFsResult} data Filesystem data (files and folders under current path) 43 | * @param {string} basePath The base physical path to calculate relative item paths from 44 | * @param {string} rootItemName Name of the root item to place non-content items under in Sitecore. Normally $siteRoot/Content. 45 | * @param {string} language Language the manifest is being created in. Conventionally affects the expected filename. 46 | * @returns {ItemDefinition} 47 | */ 48 | function convertToItems(data, basePath, rootItemName, rootItemDisplayName, language) { 49 | const itemPath = convertPhsyicalPathToItemRelativePath(data.path, basePath); 50 | const name = itemPath.substr(itemPath.lastIndexOf('/') + 1); 51 | 52 | let result; 53 | 54 | const contentItemPattern = new RegExp(`^${language}\\.(yaml|yml|json)$`, 'i'); 55 | 56 | const contentFileData = data.files.find((f) => contentItemPattern.test(f.filename)); 57 | 58 | if (contentFileData && contentFileData.contents) { 59 | // the path has a valid content item definition 60 | result = contentFileData.contents; 61 | 62 | // Set the path to the item when imported in Sitecore. 63 | // NOTE: Importing to any Sitecore path the import user has rights to is allowed; '$site/Content' is a convention only. 64 | result.path = itemPath; 65 | 66 | // content item name defaults to parent folder name if not explicit 67 | if (!result.name) { 68 | result.name = name; 69 | } 70 | } else if (data.folders.length > 0) { 71 | // The path does not have a content item definition (i.e. en.yml), 72 | // but it does have child folders (which may contain valid content items) 73 | // it will be defined as a Folder item in Sitecore. 74 | result = { 75 | path: itemPath, 76 | name: name || rootItemName, 77 | displayName: name || rootItemDisplayName || rootItemName, 78 | template: 'Folder', 79 | children: [], 80 | }; 81 | } 82 | 83 | // recursively process child paths 84 | if (data.folders.length > 0) { 85 | result.children = data.folders 86 | .map((folder) => 87 | convertToItems(folder, basePath, rootItemName, rootItemDisplayName, language) 88 | ) 89 | .filter((item) => item); // remove null results 90 | } 91 | 92 | return result; 93 | } 94 | 95 | /** 96 | * Converts a physical filesystem path into a relative Sitecore item path. 97 | * i.e. if physicalPath = /var/log and basePath = /var, this returns /log. 98 | * @param {string} physicalPath 99 | * @param {string} basePath 100 | */ 101 | function convertPhsyicalPathToItemRelativePath(physicalPath, basePath) { 102 | const targetPathSeparator = '/'; 103 | 104 | // normalize path separators to / 105 | const normalizedPath = physicalPath.replace(basePath, '').replace(/\\/g, targetPathSeparator); 106 | 107 | if (!normalizedPath) { 108 | return targetPathSeparator; 109 | } 110 | 111 | return normalizedPath.indexOf(targetPathSeparator) > 0 112 | ? `${targetPathSeparator}${normalizedPath}` 113 | : normalizedPath; 114 | } 115 | -------------------------------------------------------------------------------- /src/sitecore/definitions/dictionary.sitecore.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-unused-vars 2 | import { Manifest } from '@sitecore-jss/sitecore-jss-manifest'; 3 | import { mergeFs } from '@sitecore-jss/sitecore-jss-dev-tools'; 4 | import fs from 'fs'; 5 | 6 | /** 7 | * Reads dictionary definition file in /data/dictionary, 8 | * then emits the dictionary into the disconnected manifest. 9 | * Invoked by convention (*.sitecore.js) when `jss manifest` is run. 10 | * @param {Manifest} manifest 11 | * @returns {Promise} 12 | */ 13 | export default function addDictionaryToManifest(manifest) { 14 | const startPath = './data/dictionary'; // relative to process invocation (i.e. where package.json lives) 15 | 16 | if (!fs.existsSync(startPath)) return; 17 | 18 | // eslint-disable-next-line consistent-return 19 | return mergeFs(startPath) 20 | .then((result) => mergeDictionaryFiles(result, manifest.language)) 21 | .then((mergedDictionary) => convertToManifestDictionary(mergedDictionary)) 22 | .then((dictionary) => manifest.addDictionary(dictionary)); 23 | } 24 | 25 | function convertToManifestDictionary(mergedDictionary) { 26 | return Object.keys(mergedDictionary).map((key) => ({ 27 | key, 28 | value: mergedDictionary[key], 29 | // optional: if you wished to specify the exact ID of a dictionary item when imported, 30 | // you could pass an 'id' property here that was a GUID or unique (app-wide) string 31 | })); 32 | } 33 | 34 | /** 35 | * Maps a filesystem dictionary file into an object that represents the dictionary. 36 | * @param {MergeFsResult} data Filesystem data (files and folders under current path) 37 | * @param {string} language Language the manifest is being created in. Conventionally affects the expected filename. 38 | * @returns {object} Key-value mappings for the dictionary 39 | */ 40 | function mergeDictionaryFiles(data, language) { 41 | let dictionaryResult = {}; 42 | 43 | // regex that matches the expected dictionary file name 44 | const dictionaryFilePattern = new RegExp(`^${language}\\.(yaml|yml|json)$`, 'i'); 45 | const dictionaryFileData = data.files.find((f) => dictionaryFilePattern.test(f.filename)); 46 | 47 | if (dictionaryFileData && dictionaryFileData.contents) { 48 | // customize here to modify the dictionary or apply conventions 49 | dictionaryResult = dictionaryFileData.contents; 50 | } 51 | 52 | return dictionaryResult; 53 | } 54 | -------------------------------------------------------------------------------- /src/sitecore/definitions/placeholders.sitecore.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-unused-vars 2 | import { Manifest } from '@sitecore-jss/sitecore-jss-manifest'; 3 | 4 | /** 5 | * Adding placeholders is optional but allows setting a user-friendly display name. Placeholder Settings 6 | * items will be created for any placeholders explicitly added, or discovered in your routes and component definitions. 7 | * Invoked by convention (*.sitecore.js) when `jss manifest` is run. 8 | * @param {Manifest} manifest 9 | */ 10 | export default function addPlaceholdersToManifest(manifest) { 11 | manifest.addPlaceholder({ name: 'uniform-jss-content', displayName: 'uniform-jss-content' }); 12 | } 13 | -------------------------------------------------------------------------------- /src/sitecore/definitions/routes.sitecore.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | import path from 'path'; 3 | import { Manifest, RouteDefinition, CommonFieldTypes } from '@sitecore-jss/sitecore-jss-manifest'; 4 | import { mergeFs, MergeFsResult } from '@sitecore-jss/sitecore-jss-dev-tools'; 5 | 6 | /* eslint-enable no-unused-vars */ 7 | 8 | /** 9 | * Collects the disconnected routes defined in data/routes into the manifest. 10 | * Invoked by convention (*.sitecore.js) when `jss manifest` is run. 11 | * Alter this method if you wish to store disconnected route data in some way other than the default, 12 | * or to preprocess the route data before it is sent to Sitecore to be ingested - for example to add fields to the route type. 13 | * @param {Manifest} manifest The manifest instance to add routes to 14 | * @returns {Promise} 15 | */ 16 | export default function addRoutesToManifest(manifest) { 17 | // Configure the default route type for the app 18 | // this lets us enable route-level data fields, 19 | // which most apps will want for metadata like page titles, SEO metas, or OpenGraph. 20 | // You can add additional non-default route types using `manifest.addRouteType()`, 21 | // which routes can use by setting `template: YourCustomRouteTypeName` in their definition. 22 | const appTemplateSection = 'Page Metadata'; 23 | 24 | manifest.setDefaultRouteType({ 25 | name: 'CommonPage', 26 | fields: [ 27 | { 28 | name: 'pageTitle', 29 | displayName: 'Page Title', 30 | section: appTemplateSection, 31 | type: CommonFieldTypes.SingleLineText, 32 | }, 33 | ], 34 | icon: 'Applications/16x16/document_plain.png', 35 | insertOptions: ['CommonPage'], 36 | }); 37 | 38 | return mergeFs('./data/routes') // relative to process invocation (i.e. your package.json) 39 | .then((result) => convertToRoutes(result, manifest.language)) 40 | .then((routeData) => { 41 | manifest.addRoute(routeData); 42 | }); 43 | } 44 | 45 | /** 46 | * Maps filesystem data into manifest route data. 47 | * This is where custom conventions regarding route data would go. 48 | * @param {MergeFsResult} data Filesystem data (files and folders under current path) 49 | * @param {string} language Language the manifest is being created in. Conventionally affects the expected filename. 50 | * @returns {RouteDefinition} 51 | */ 52 | function convertToRoutes(data, language) { 53 | let routeData; 54 | 55 | // regex that matches the expected route file name 56 | const routeFilePattern = new RegExp(`^${language}\\.(yaml|yml|json)$`, 'i'); 57 | 58 | // find the expected file in the list of files in the current folder 59 | const routeFileData = data.files.find((f) => routeFilePattern.test(f.filename)); 60 | 61 | // parse the route data file contents 62 | if (routeFileData && routeFileData.contents) { 63 | routeData = routeFileData.contents; 64 | 65 | if (!routeData.name) { 66 | // no name = imply one from parent folder name 67 | routeData.name = path.basename(path.dirname(routeFileData.path)); 68 | // special case for the home route item as its parent folder is 'routes' 69 | if (routeData.name === 'routes') routeData.name = 'home'; 70 | } 71 | } else { 72 | console.warn( 73 | `Route data file not found: ${data.path}\\${language}.(yaml|yml|json). 74 | The route will not be added to the manifest. Empty folders can cause this warning.` 75 | ); 76 | } 77 | 78 | // recursively crawl child routes (folders) 79 | if (routeData && data.folders.length > 0) { 80 | routeData.children = data.folders 81 | .map((folder) => convertToRoutes(folder, language)) 82 | .filter((route) => route); // remove null results 83 | } 84 | 85 | return routeData; 86 | } 87 | -------------------------------------------------------------------------------- /src/sitecore/pipelines/generateMedia.patch.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra'; 2 | import nodePath from 'path'; 3 | 4 | // This pipeline patch allows us to hook into the JSS manifest generation process. 5 | // Specifically, we add a pipeline processor to the `generateMedia` pipeline. 6 | // The processor is intended to run after all other `generateMedia` processors and will 7 | // copy the media items identified in the manifest to the `/public` folder for static export. 8 | 9 | // This usage of `resolve` assumes that the manifest generation process is invoked from 10 | // the project root folder and the `./public` folder is relative to the project root folder. 11 | const staticExportAssetFolderPath = nodePath.resolve('./public'); 12 | 13 | export const config = (pipelines) => { 14 | // We only want to execute our custom pipeline processor when the app is being statically exported. 15 | if (process.env.SITE_RUNTIME_ENV !== 'static') { 16 | return; 17 | } 18 | 19 | const pipeline = pipelines.getPipeline('generateMedia'); 20 | 21 | pipeline.addProcessor({ 22 | name: 'copyMediaToPublic', 23 | process: (processorArgs) => { 24 | if (!processorArgs.media || !Array.isArray(processorArgs.media)) { 25 | return processorArgs; 26 | } 27 | 28 | processorArgs.media.forEach((media) => { 29 | if (!media.src) { 30 | console.warn( 31 | `Media object ${JSON.stringify( 32 | media 33 | )} did not have an expected 'src' property. Its media item will not be deployed.` 34 | ); 35 | return; 36 | } 37 | 38 | const mediaSourcePath = nodePath.isAbsolute(media.src) ? `.${media.src}` : media.src; 39 | 40 | if (fs.existsSync(mediaSourcePath)) { 41 | if (!fs.statSync(mediaSourcePath).isFile()) { 42 | console.warn( 43 | `Source media path referred to in manifest data is not a file: ${mediaSourcePath}` 44 | ); 45 | return; 46 | } 47 | const mediaDestinationPath = nodePath.join(staticExportAssetFolderPath, media.src); 48 | const mediaDestinationFolder = nodePath.dirname(mediaDestinationPath); 49 | fs.ensureDirSync(mediaDestinationFolder); 50 | fs.copySync(mediaSourcePath, mediaDestinationPath); 51 | console.log( 52 | `Copied media for static export from: ${mediaSourcePath} to: ${mediaDestinationPath}` 53 | ); 54 | return { source: mediaSourcePath, destination: mediaDestinationPath, success: true }; 55 | } 56 | }); 57 | 58 | return processorArgs; 59 | }, 60 | }); 61 | }; 62 | -------------------------------------------------------------------------------- /src/static/README.md: -------------------------------------------------------------------------------- 1 | # STATIC 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | This directory contains your static files. 6 | Each file inside this directory is mapped to `/`. 7 | Thus you'd want to delete this README.md before deploying to production. 8 | 9 | Example: `/static/robots.txt` is mapped as `/robots.txt`. 10 | 11 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/assets#static). 12 | -------------------------------------------------------------------------------- /src/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uniformdev/sitecore-jss-nuxtjs-starterkit/f1e2ea584da52e02fb48af6297811da5abb88bf3/src/static/favicon.ico -------------------------------------------------------------------------------- /src/store/app/index.js: -------------------------------------------------------------------------------- 1 | import { createLayoutServiceClient } from '../../lib/layoutServiceUtils'; 2 | import { getConfig } from '../../temp/config'; 3 | 4 | export const state = () => ({ 5 | sitecoreContext: {}, 6 | routeData: null, 7 | currentRoute: '', 8 | routeDataFetchStatus: '', 9 | routeDataFetchError: null, 10 | }); 11 | 12 | export const mutations = { 13 | setLayoutData(state, { layoutData }) { 14 | if (!layoutData) { 15 | return; 16 | } 17 | 18 | const routeData = layoutData.sitecore && layoutData.sitecore.route; 19 | state.routeData = routeData; 20 | 21 | const context = (layoutData.sitecore && layoutData.sitecore.context) || {}; 22 | state.sitecoreContext = { 23 | ...context, 24 | routeName: routeData && routeData.name, 25 | itemId: routeData && routeData.itemId, 26 | }; 27 | }, 28 | setCurrentRoute(state, { route }) { 29 | state.currentRoute = route; 30 | }, 31 | setRouteDataFetchStatus(state, { status, error }) { 32 | state.routeDataFetchStatus = status; 33 | state.routeDataFetchError = error; 34 | }, 35 | }; 36 | 37 | export const actions = { 38 | getLayoutData(context, { route, language, nuxtContext }) { 39 | const { req } = nuxtContext; 40 | 41 | // If the incoming request exists it means we're in SSR. 42 | // If the incoming request has a `jssData` property, it means the app 43 | // is responding to either a proxy request or a JSS rendering host request. 44 | // In either case, we set layout data from the `jssData` property instead 45 | // of trying to fetch data from Layout Service. 46 | if (req && req.jssData) { 47 | context.commit('setLayoutData', { layoutData: req.jssData.route }); 48 | context.commit('setCurrentRoute', { route }); 49 | return Promise.resolve(); 50 | } else { 51 | // This is a client-side request for layout data, e.g. route change. 52 | const config = getConfig(); 53 | const layoutServiceClient = createLayoutServiceClient(config, { nuxtContext }); 54 | 55 | context.commit('setRouteDataFetchStatus', { status: 'loading', error: null }); 56 | return layoutServiceClient 57 | .getRouteData(route, language) 58 | .then((layoutData) => { 59 | context.commit('setLayoutData', { layoutData }); 60 | context.commit('setCurrentRoute', { route }); 61 | context.commit('setRouteDataFetchStatus', { status: '', error: null }); 62 | }) 63 | .catch((error) => { 64 | if (error.response && error.response.data && error.response.data.sitecore) { 65 | context.commit('setLayoutData', { layoutData: error.response.data }); 66 | } 67 | context.commit('setCurrentRoute', { route }); 68 | context.commit('setRouteDataFetchStatus', { status: 'error', error }); 69 | }); 70 | } 71 | }, 72 | nuxtServerInit(nuxtContext) { 73 | // implement any server-specific actions here 74 | }, 75 | }; 76 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | export const actions = { 2 | // `nuxtServerInit` is only invoked if it is defined in the `store/index.js` file. It will not be automatically 3 | // invoked if it is defined in a store module, e.g. `store/app/index.js` 4 | // That said, we can still define a `nuxtServerInit` within a store module and simply call it from the "root" 5 | // `nuxtServerInit` function, like below. 6 | // https://nuxtjs.org/guide/vuex-store#the-nuxtserverinit-action 7 | nuxtServerInit(context, nuxtContext) { 8 | context.dispatch('app/nuxtServerInit', nuxtContext); 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /src/store/ui/index.js: -------------------------------------------------------------------------------- 1 | export const state = () => ({}); 2 | 3 | export const mutations = {}; 4 | 5 | export const actions = {}; 6 | -------------------------------------------------------------------------------- /src/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | ** TailwindCSS Configuration File 3 | ** 4 | ** Docs: https://tailwindcss.com/docs/configuration 5 | ** Default: https://github.com/tailwindcss/tailwindcss/blob/master/stubs/defaultConfig.stub.js 6 | */ 7 | module.exports = { 8 | theme: {}, 9 | variants: {}, 10 | plugins: [], 11 | purge: { 12 | // Learn more on https://tailwindcss.com/docs/controlling-file-size/#removing-unused-css 13 | // IMPORTANT: the following settings are _necessary_ in order for Nuxt to not bundle 14 | // all CSS. 15 | enabled: process.env.NODE_ENV === 'production', 16 | content: [ 17 | 'components/**/*.vue', 18 | 'layouts/**/*.vue', 19 | 'pages/**/*.vue', 20 | 'plugins/**/*.js', 21 | 'nuxt.config.js', 22 | ], 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /src/temp/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | !GraphQLFragmentTypes.json 4 | -------------------------------------------------------------------------------- /src/uniform.config.js: -------------------------------------------------------------------------------- 1 | const dotenv = require('dotenv'); 2 | const { parseUniformConfig } = require('@uniformdev/common'); 3 | const { parseUniformServerConfig } = require('@uniformdev/common-server'); 4 | 5 | // This file is not required, but it provides default standard values for the starter kit. 6 | // You can override the values via a `.env` file if necessary or set the environment variables 7 | // prior to app start. 8 | const defaultConfig = { 9 | PORT: 3000, 10 | UNIFORM_API_DEFAULT_LANGUAGE: 'en', 11 | UNIFORM_API_SITENAME: 'uniform-jss-kit', 12 | UNIFORM_API_URL: 'http://localhost:3000', 13 | UNIFORM_DATA_URL: 'http://localhost:3000', 14 | UNIFORM_OPTIONS_DEBUG: false, 15 | UNIFORM_OPTIONS_PREFETCH_LINKS: false, 16 | UNIFORM_OPTIONS_MVC_SPA_ENABLED: false, 17 | UNIFORM_OPTIONS_MVC_SUPPORT: false, 18 | }; 19 | 20 | const defaultServerConfig = { 21 | ...defaultConfig, 22 | UNIFORM_API_TOKEN: '12345', 23 | UNIFORM_MODE: 'mixed', 24 | UNIFORM_PUBLISH_FAKE_PUBLIC_URL: 'http://localhost:3000', 25 | UNIFORM_PUBLISH_PREFETCH_ENABLED: false, 26 | UNIFORM_PUBLISH_TARGET: 'none', 27 | }; 28 | 29 | module.exports = { 30 | getUniformConfig() { 31 | setEnvVars(defaultConfig); 32 | const uniformConfig = parseUniformConfig(process.env); 33 | return uniformConfig; 34 | }, 35 | getUniformServerConfig() { 36 | setEnvVars(defaultServerConfig); 37 | const uniformServerConfig = parseUniformServerConfig(process.env); 38 | return uniformServerConfig; 39 | }, 40 | }; 41 | 42 | function setEnvVars(config) { 43 | dotenv.config(); 44 | 45 | Object.keys(config).forEach((configKey) => { 46 | process.env[configKey] = process.env[configKey] || config[configKey]; 47 | }); 48 | } 49 | --------------------------------------------------------------------------------