If JSS is a new term for you, I’d seriously recommend checking our the documentation that Sitecore have provided: https://jss.sitecore.com/ .
By the end of this post we’ll have run through how you can get JSS up and running locally, with dependencies all wired together using a DI container and any functional aspects written in TypeScript. For us this is a key set of requirements – we’ve worked with many projects that have grown over several years. By putting in some key rules and requirements up front should mean with good discipline that the codebase can scale over time.
Why JSS?
Imagine a standard Sitecore development team, typically based around C# developers and some front end devs. In the default approach to building a site you’d need everyone to contribute Razor files with the markup and associated styling and functionality. This is the approach you would probably have seen for several years until more recently with the demand for richer front end technologies. Think Vue, Angular, React and so on.
This is what JSS facilitates.
Is this right for everyone?
Just because technologies exist, it doesn’t always make them the right platform to jump on. E.g. if you have a very established Sitecore development team that doesn’t have the appetite for these front end technologies, then JSS might not be the thing for you.
Getting started
The quick start from the docs site provides 4 tasks to get you started:
1 2 3 4 |
npm install -g @sitecore-jss/sitecore-jss-cli jss create my-first-jss-app vue cd my-first-jss-app jss start |
Provided you have node installed, once you run ^ you should then see http://localhost:3000 fire up.
Why TypeScript?
I wouldn’t consider starting a new web project now without TypeScript as it provides so many useful features for a codebase. Refactoring is just like if you were using C#, variables have types, classes can implement other abstract classes or interfaces. If you’ve not used it before, I’d highly recommend it.
In terms of designing your application, another key factor to consider is the coupling between the different layers. Core functionality being one layer, your UI framework being another. If you structure things so that e.g. you can peel out Vue without too much trouble, moving up through different technologies or versions will be a breeze.
Changes to the default app
Here we’ll add things like some demo services, some DI registrations and a few other bits we’ll need.
1.First up lets include some extra dependencies:
1 |
npm install -d inversify-inject-decorators, vue-class-component, inversify, vue-property-decorator, reflect-metadata, ts-loader, typescript |
2. In src/AppRoot.vue, before the export default
line add import "reflect-metadata"
3. Add a tsconfig.json file to the root folder (a level above src):
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 |
{ "compilerOptions": { "outDir": "./built/", "sourceMap": true, "strict": true, "module": "es2015", "moduleResolution": "node", "target": "es5", "lib": [ "es6", "dom" ], "types": [ "reflect-metadata" ], "experimentalDecorators": true, "emitDecoratorMetadata": true, "baseUrl": "./src", "paths": { "@di_ids": ["DependencyInjection/Identifiers"] } }, "include": [ "./src/**/*" ] } |
4. Update the webpack config, in the Vue world this is done in vue.config.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
//at the top of the file: let path= require('path'); //alongside the graphql includes: config.module.rules.push({ test: /\.tsx?$/, loader: 'ts-loader', exclude: /node_modules/, options: { appendTsSuffixTo: [/\.vue$/], } }); config.resolve.extensions.push('.ts'); //note, this is required for alias'ing to work for your references - a nice way to avoid crazy import statements. Same change exists in tsconfig config.resolve.alias["@di_ids"] = path.resolve(__dirname, 'src/DependencyInjection/Identifiers') |
5. Now add a vue-shim.d.ts
(in the src folder)
1 2 3 4 |
declare module "*.vue" { import Vue from "vue"; export default Vue; } |
6. Next, some dummy TypeScript dependencies:
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 28 29 30 31 32 33 34 35 36 37 38 39 40 |
// /src/scripts/processors/TestProcessor.ts import { IService } from "../services/TestService"; import { inject, injectable } from 'inversify'; import {SERVICE_IDENTIFIER} from "@di_ids"; @injectable() export default class TestProcessor { constructor(@inject(SERVICE_IDENTIFIER.IService) private service:IService) { } public DoSomething() : string { return this.service.DoIt(); } } // /src/scripts/services/TestServices.ts import { injectable } from 'inversify'; @injectable() export class ServiceA implements IService { DoIt(): string { return "ServiceA"; } } @injectable() export class ServiceB implements IService { DoIt(): string { return "ServiceB"; } } export interface IService { DoIt(): string; } |
7. And the DI container and keys:
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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
// /src/DependencyInjection/ContainerRoot.ts import { Container } from "inversify"; import getDecorators from 'inversify-inject-decorators'; import TestModule from './Modules/TestModule' let container = new Container(); container.load(TestModule); const { lazyInject } = getDecorators(container); export { container, lazyInject } // /src/DependencyInjection/Identifiers.ts const SERVICE_IDENTIFIER = { IService: Symbol("IService"), Container: Symbol("Container"), TestProcessor: Symbol("TestProcessor") }; export {SERVICE_IDENTIFIER} // /src/DependencyInjection/Modules/TestModule.ts import { ContainerModule, interfaces } from "inversify"; import { SERVICE_IDENTIFIER } from "../Identifiers"; import { IService, ServiceA, ServiceB } from "../../scripts/services/TestService"; import TestProcessor from "../../scripts/processors/TestProcessor"; const testServices = new ContainerModule( ( bind: interfaces.Bind ) => { bind<IService>(SERVICE_IDENTIFIER.IService).to(ServiceA); bind<TestProcessor>(SERVICE_IDENTIFIER.TestProcessor).to(TestProcessor); } ); export default testServices; |
8. Now a TypeScript enabled Vue component: /src/components/Hello.vue
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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
<template> <div> <div class="greeting">Hello {{name}}{{exclamationMarks}}</div> <button @click="decrement">-</button> <button @click="increment">+</button> </div> </template> <script lang="ts"> import { Vue, Component, Prop, Inject } from 'vue-property-decorator' import { Container } from "inversify"; import TestProcessor from '../scripts/processors/TestProcessor'; import { SERVICE_IDENTIFIER } from "@di_ids"; import { inject, injectable } from 'inversify'; import { lazyInject } from '../DependencyInjection/ContainerRoot'; @Component({ //components: { SubComponent } }) export default class Hello extends Vue { @lazyInject(SERVICE_IDENTIFIER.TestProcessor) private _testProcessor!: TestProcessor; // // If you want to use Vue's OTB Inject/Provide you can // @Inject(SERVICE_IDENTIFIER.Container) // private _container!: Container; created (): void { //console.log(this._container.resolve<TestProcessor>(TestProcessor).DoSomething()); console.log(this._testProcessor.DoSomething()); } @Prop() name!: string; @Prop() initialEnthusiasm!: number; enthusiasm = this.initialEnthusiasm; increment() { this.enthusiasm++; } decrement() { if (this.enthusiasm > 1) { this.enthusiasm--; } } get exclamationMarks(): string { return Array(this.enthusiasm + 1).join('!'); } } </script> <style> .greeting { font-size: 20px; } </style> |
9. And to finally get it showing on a page, edit layout.vue to include your component:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<div class="container"> <!-- <placeholder name="jss-main" :rendering="route" /> --> <Hello :initialEnthusiasm="5" /> </div> //... import Hello from './components/Hello' //... components: { //..., Hello }, |
After all that, you should see the homepage load up and “ServiceA” getting logged to the console. Not the most impressive output but shows the full flow of dependencies getting configured and resolved, with all the code written in TypeScript 🙂
If you are using SSR Renderings, you’ll also need to add |ts
into the list of rules that get ‘unshift’ed in /server/server.vue.config.js