January 29, 2019 • ☕️☕️ 12 min read
In this blog post I describe my adventures to convert an “out of the box” Sitecore 9.1 with JSS in React with TypeScript project from scratch. To get started head over to the Quickstart |Sitecore JSS Documentation and follow the steps to create a disconnected JSS starter project:
npm install -g @sitecore-jss/sitecore-jss-cli
jss create hello-jss-typescript react
git init
cd hello-jss-typescript
git init
git add -A
git commit -m "first commit"
git remote add origin https://github.com/macaw-interactive/hello-jss-sitecore.git
git push -u origin master
jss start
We now have the starter website running on http://localhost:3000.
Now we can scaffold a new Hero component using jss scaffold Hero
.
The scaffolder generates the new component in it’s own folder src/components/Hero
named index.js
.
The easy way to introduce TypeScript is described by my colleague Gary Wenneker in his blog post Sitecore JSS: Get Typed!:
Hero.js
to Hero.tsx
npm install typescript @types/node @types/react @types/react-dom
tsconfig.json
tsc --watch
to compile the .ts
and .tsx
files to .js
next to the source fileThe JSS tooling picks up the .js
component files in the script "start:watch-components": "node scripts/generate-component-factory.js --watch"
for mapping the React components to JSS components.
Problem with this approach is that source tree is cluttered with generated .js
files
next to the TypeScript files that are difficult to exclude from source control.
In this blog post we describe another approach in using TypeScript in you Sitecore JSS project. The blog post is accompanied by the GitHub repository hello-jss-typescript with the required modifications for a simple “Hello world” app as a reference for you adventures.
The first step to execute is to install the required TypeScript tooling and type definitions:
npm install typescript @types/node @types/react @types/react-dom
Now add a tsconfig.json
file in the root tailered for JSS development.
Because the JSS tool-set is based on the magnificent tool-set of Create React App we took as a starter point the
tsconfig.json
as generated by create-react-app
. We need to make some modifications to tailer it to the needs of the JSS project. Those changes are already
included is the tsconfig.json
below. The rationale behind these changes are described below where appropriate.
{
"compilerOptions": {
"target": "es5",
"allowJs": false,
"skipLibCheck": false,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "preserve",
"lib": ["es2018", "dom"],
},
"include": [
"src"
],
"types": ["node"]
}
We now scaffold the Hero
component using the command jss scaffold Hero
as
described in Scaffolding a JSS Component.
This will generate two files:
src\components\Hero\index.js
sitecore\definitions\components\Hero.sitecore.js
The generated src\components\Hero\index.js
looks like:
import React from 'react';
import { Text } from '@sitecore-jss/sitecore-jss-react';
const Hero = (props) => (
<div>
<p>Hero Component</p>
<Text field={props.fields.heading} />
</div>
);
export default Hero;
rename the file to src\components\Hero\index.tsx
and rewrite the code to:
import * as React from 'react';
import { Text } from '@sitecore-jss/sitecore-jss-react';
type HeroPropsFields = {
heading: {
value?: string;
editable?: string;
}
}
type HeroProps = {
fields: HeroPropsFields;
};
type HeroAllProps = HeroProps;
const Hero: React.SFC<HeroAllProps> = (props: HeroAllProps) => (
<div>
<p>Hero Component</p>
<Text field={props.fields.heading} />
</div>
);
export default Hero;
To include the Hero component in a route layout in disconnected mode open the file data\routes\en.yml
and add the Hero component to the jss-main
placeholder:
placeholders:
jss-main:
- componentName: Hero
fields:
heading: Serge's hero component!
- componentName: ContentBlock
fields:
The component factory does the mapping op React components to JSS components. It provides the mapping between a string name based on the component folder and name the React component instance. When the Sitecore Layout service returns a layout definition, it returns the components by name. Based on the mapping provided by the component factory the component hierarchy can be constructed for the layout.
The script scripts/generate-component-factory.js
is responsible for the generation of
this mapping and produces the componentFactory.js
output file. We want to use TypeScript for this file because we want the generated import statements also to search for .ts
and .tsx
files. must be changed to the .ts
extensions. This means that we need to change
the output file to componentFactory.ts
. We also want the component factory generation script
to pick up .ts
and .tsx
files so components can be written in both JavaScript and TypeScript. This means that we need to add the following lines to the script:
const componentFactoryPath = path.resolve('src/temp/componentFactory.ts');
and
fs.existsSync(path.join(componentFolderFullPath, 'index.js')) ||
fs.existsSync(path.join(componentFolderFullPath, 'index.jsx')) ||
fs.existsSync(path.join(componentFolderFullPath, 'index.ts')) || fs.existsSync(path.join(componentFolderFullPath, 'index.tsx'))
Rewriting the JSS tool scripts like scripts/generate-component-factory.js
to TypeScript
as well makes no sense because they are node scripts, and we don’t want to introduce a
compilation step in order to run them, although ts-node is a viable solution for directly running TypeScript files in a development environment. Another
reason is that the JSS tool-set calls these scripts.
On compilation of the componentFactory.ts
file we got the error:
Type error: Could not find a declaration file for module '../components/Hero'. 'C:/P/hello-jss-typescript/src/components/Hero/index.js' implicitly has an 'any' type. TS7016
We got similar errors on more files. The easiest way to resolve this is to set the option
"noImplicitAny": false
in the tsconfig.json
. This is not a really sustainable option
because using types instead of any
is the whole purpose of using TypeScript, and settings this
option means that also the application specific code is not checked if types are omitted.
Make sure that you don’t have an old src/temp/componentFactory.js
file because this file will be picked up in the compilation as well.
We can now run jss start
again and get our TypeScript Hero component working in the disconnected site:
Now it’s time to deploy our JSS app to Sitecore as described in JSS Server Setup.
Part of these steps as described in App Deployment is:
hello-jss-typescript.dev.local
127.0.0.1 hello-jss-typescript.dev.local
to the file C:\Windows\System32\drivers\etc\hosts
After everything is configured we can run jss setup
and answer the questions (you answers may vary):
Is your Sitecore instance on this machine or accessible via network share? [y/n]: y
Path to the Sitecore folder (e.g. c:\inetpub\wwwroot\my.siteco.re): c:\inetpub\wwwroot\sc910.sc
Sitecore hostname (e.g. http://myapp.local.siteco.re; see /sitecore/config; ensure added to hosts): http://hello-jss-typescript.dev.local
Sitecore import service URL [http://hello-jss-typescript.dev.local/sitecore/api/jss/import]:
Sitecore API Key (ID of API key item): {57231674-4CC9-48AA-AFF0-190DB9D68FE1}
Please enter your deployment secret (32+ random chars; or press enter to generate one):
Deployment secret has been generated. Ensure the JSS app config on the Sitecore end has the same secret set.
Deploy secret Sitecore config written to C:\P\hello-jss-typescript\sitecore\config\hello-jss-typescript.deploysecret.config
Ensure this configuration is deployed to Sitecore.
JSS connection settings saved to C:\P\hello-jss-typescript\scjssconfig.json
NEXT STEPS
* Ensure the hostName in /sitecore/config/*.config is configured as hello-jss-typescript.dev.local, and in hosts file if needed.
* Deploy your configuration (i.e. 'jss deploy config')
* Deploy your app (i.e. 'jss deploy app -c -d')
* Test your app in integrated mode by visiting http://hello-jss-typescript.dev.local
As specified in the NEXT STEPS
we should deploy the configuration using jss deploy config
and deploy the app using jss deploy app -c -d
.
When we deploy the app we get a compilation error in building the server.bundle.js
:
ERROR in ./src/AppRoot.js
Module not found: Error: Can't resolve './temp/componentFactory' in 'C:\P\hello-jss-typescript\src'
@ ./src/AppRoot.js 5:0-55 32:22-38
@ ./server/server.js
Because server/server.js
does an import of AppRoot
from ../src/AppRoot
and
AppRoot
does an import of ./temp/componentFactory
which is generated as a
TypeScript file we have an issue. This can be fixed by renaming server/server.js
to server/server.tsx
and modifying server/server.webpack.config.js
to have as
entry entry: path.resolve(__dirname, './server.tsx')
. Because the server bundle specific
webpack configuration file does a require of babel-preset-react-app
it can use the
Babel 7 configuration to compile TypeScript files out of the box.
Next step is to rename the file src/AppRoot.js
to src/AppRoot.tsx
. This is the main
entry point of the app invoked by the renderer for server and client rendering. It imports
the generated src/temp/componentFactory
file, but we can’t import a .ts
file
from a .js
file. It won’t be resolved.
For the Typescript compilation of the server bundle we need the same tsconfig.json
configuration
as for the client bundle, with only the include path changed. For this we can create the file
server/tsconfig
with the following contents:
{
"extends": "../tsconfig.json",
"include": [
"."
]
}
Including the content of the above described files will make this blog post too large, so please refer to the GitHub repository hello-jss-typescript for the details.
If we now the app using jss deploy app -c -d
we can navigate to http://hello-jss-typescript.dev.local/ and
the site is rendered correctly.
The layout of the rendered React page for a given route is provided by the Sitecore layout service.
In our case a hit on http://hello-jss-typescript.dev.local/sitecore/api/layout/render/jss?item=/&sc_apikey={57231674-4CC9-48AA-AFF0-190DB9D68FE1}
(your API key will vary) gives us:
{
"sitecore": {
"context": {
"pageEditing": false,
"site": {
"name": "hello-jss-typescript"
},
"pageState": "normal",
"language": "en"
},
"route": {
"name": "home",
"displayName": "home",
"fields": {
"pageTitle": {
"value": "Welcome to Sitecore JSS"
}
},
"databaseName": "master",
"deviceId": "fe5d7fdf-89c0-4d99-9aa3-b5fbd009c9f3",
"itemId": "75b2a549-f227-51dd-98dd-599438514aad",
"itemLanguage": "en",
"itemVersion": 1,
"layoutId": "a0909742-4629-5ae4-a71a-1fe76a57379a",
"templateId": "ab82556d-8208-5edd-a980-be88546ccf5b",
"templateName": "App Route",
"placeholders": {
"jss-main": [
{
"uid": "3af894c3-37f9-52fa-943b-561cdf38da1a",
"componentName": "Hero",
"dataSource": "{0DC131EC-C855-5F8B-BFB9-62606ABB2DF2}",
"fields": {
"heading": {
"value": "Serge's hero component!"
}
}
},
{
"uid": "7418112d-b1c0-53fe-9256-b807b16fe3f4",
"componentName": "ContentBlock",
"dataSource": "{20448A26-84CB-52B7-BCE7-8D4851E8AABC}",
"fields": {
"heading": {
"value": "Welcome to Sitecore JSS"
},
"content": {
"value": "<p>Thanks for using JSS. Here are some resources to get you started:</p>\n\n<h3><a href=\"https://jss.sitecore.net\" rel=\"noopener noreferrer\">Documentation</a></h3>\n<p>The official JSS documentation can help you with any JSS task from getting started to advanced techniques.</p>\n\n<h3><a href=\"/styleguide\">Styleguide</a></h3>\n<p>The JSS styleguide is a living example of how to use JSS, hosted right in this app.\nIt demonstrates most of the common patterns that JSS implementations may need to use,\nas well as useful architectural patterns.</p>\n\n<h3><a href=\"/graphql\">GraphQL</a></h3>\n<p>JSS features integration with the Sitecore GraphQL API to enable fetching non-route data from Sitecore - or from other internal backends as an API aggregator or proxy.\nThis route is a living example of how to use an integrate with GraphQL data in a JSS app.</p>\n\n<div class=\"alert alert-dark\">\n <h4>This app is a boilerplate</h4>\n <p>The JSS samples are a boilerplate, not a library. That means that any code in this app is meant for you to own and customize to your own requirements.</p>\n <p>Want to get change the lint settings? Do it. Want to read manifest data from a MongoDB database? Go for it. This app is yours.</p>\n</div>\n\n<div class=\"alert alert-dark\">\n <h4>How to start with an empty app</h4>\n <p>To remove all of the default sample content (the Styleguide and GraphQL routes) and start out with an empty JSS app:</p>\n <ol>\n <li>Delete <code>/src/components/Styleguide*</code> and <code>/src/components/GraphQL*</code></li>\n <li>Delete <code>/sitecore/definitions/components/Styleguide*</code>, <code>/sitecore/definitions/templates/Styleguide*</code>, and <code>/sitecore/definitions/components/GraphQL*</code></li>\n <li>Delete <code>/data/component-content/Styleguide</code></li>\n <li>Delete <code>/data/content/Styleguide</code></li>\n <li>Delete <code>/data/routes/styleguide</code> and <code>/data/routes/graphql</code></li>\n <li>Delete <code>/data/dictionary/*.yml</code></li>\n </ol>\n</div>\n"
}
}
}
]
}
}
}
}
Based on this JSON the final page is built.
This is just the first step… all app specific code must be rewritten in TypeScript. I will keep you posted.
Interesting stuff to investigate in this context:
Discuss on Twitter • Edit on GitHub
Personal blog by Serge van den Oever - als je maar lol hebt...