I decided that I wanted to try building a self contained Stencil component ready to use by anyone on the npm repository.
My idea was to create a web component that is heavily inspired by the Angular’s ng-container.
The plan was that st-container would have two attributes,
- st-if, only shows the content inside the st-container if the given condition is true.
- st-for, iterate over an array/list of items with the content like sort of a template.
Creating the project
So I started by cloning the Stencil component starter repository,
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
git clone https://github.com/ionic-team/stencil-component-starter stencil-container | |
cd my-component | |
git remote rm origin | |
npm install |
then I added the boilerplate to my own repository with command,
git remote add origin https://github.com/nerdic-coder/stencil-container.git
then I updated all the information in the package.json, README.md, stencil.config.js to describe my component instead of describing Stencils starter project.
In the package.json I updated the name, description, repository, author, bugs, homepage like this:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"name": "stencil-container", | |
"version": "0.0.2", | |
"description": "Stencil Container with if-statements and for-loops", | |
"module": "dist/esm/index.js", | |
"main": "dist/index.js", | |
"types": "dist/types/components.d.ts", | |
"collection": "dist/collection/collection-manifest.json", | |
"files": [ | |
"dist/" | |
], | |
"scripts": { | |
"build": "stencil build", | |
"dev": "sd concurrent \"stencil build –dev –watch\" \"stencil-dev-server\" ", | |
"serve": "stencil-dev-server", | |
"start": "npm run dev", | |
"test": "jest", | |
"test.watch": "jest –watch" | |
}, | |
"dependencies": {}, | |
"devDependencies": { | |
"@stencil/core": "^0.9.1", | |
"@stencil/dev-server": "latest", | |
"@stencil/utils": "latest", | |
"@types/jest": "^21.1.1", | |
"jest": "^21.2.1" | |
}, | |
"repository": { | |
"type": "git", | |
"url": "git+https://github.com/nerdic-coder/stencil-container.git" | |
}, | |
"author": "Johan Axelsson", | |
"license": "MIT", | |
"bugs": { | |
"url": "https://github.com/nerdic-coder/stencil-container/issues" | |
}, | |
"homepage": "https://github.com/nerdic-coder/stencil-container", | |
"jest": { | |
"transform": { | |
"^.+\\.(ts|tsx)$": "<rootDir>/node_modules/@stencil/core/testing/jest.preprocessor.js" | |
}, | |
"testRegex": "(/__tests__/.*|\\.(test|spec))\\.(tsx?|jsx?)$", | |
"moduleFileExtensions": [ | |
"ts", | |
"tsx", | |
"js", | |
"json", | |
"jsx" | |
] | |
} | |
} |
In the stencil.config.js I just changed the namespace to stencil-container.
Building the component
Now we are ready to create the stencil component in the project.
First we need to create the component folder, go ahead and create a folder named ‘st-container’ inside ‘/src/components/’ folder.
In that folder we will create two files,
st-container.spec.ts, that contains the unit tests for the component.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { TestWindow } from '@stencil/core/testing'; | |
import { StContainer } from './st-container'; | |
describe('st-container', () => { | |
it('should build', () => { | |
expect(new StContainer()).toBeTruthy(); | |
}); | |
describe('rendering', () => { | |
let element; | |
beforeAll(async () => { | |
const window = new TestWindow(); | |
element = await window.load({ | |
components: [StContainer], | |
html: '<st-container><div>Hello, World!</div></st-container>' | |
}); | |
}); | |
it('should work without parameters', () => { | |
expect(element.textContent.trim()).toEqual('Hello, World!'); | |
}); | |
it('should work with st-if true', async () => { | |
const window = new TestWindow(); | |
element = await window.load({ | |
components: [StContainer], | |
html: '<st-container st-if="true"><div>Hello, World!</div></st-container>' | |
}); | |
expect(element.stIf).toEqual('true'); | |
expect(element.shouldRender).toBeTruthy(); | |
expect(element.textContent.trim()).toEqual('Hello, World!'); | |
}); | |
it('should not work with st-if false', async () => { | |
const window = new TestWindow(); | |
element = await window.load({ | |
components: [StContainer], | |
html: '<st-container st-if="false"><div>Hello, World!</div></st-container>' | |
}); | |
expect(element.stIf).toEqual('false'); | |
expect(element.shouldRender).toBeFalsy(); | |
}); | |
}); | |
}); |
st-container.tsx, the stencil component itself.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Component, Prop, Watch } from '@stencil/core'; | |
@Component({ | |
tag: 'st-container', | |
shadow: true | |
}) | |
export class StContainer { | |
@Prop() private stIf: string; | |
@Prop({ mutable: true }) private shouldRender = true; | |
@Watch('stIf') | |
watchHandler(newValue: string) { | |
console.log('The new value of stIf is: ', newValue); | |
this.shouldRender = false; | |
this.shouldRender = new Function("return " + this.stIf)(); | |
} | |
/** | |
* The component is about to load and it has not | |
* rendered yet. | |
* | |
* This is the best place to make any data updates | |
* before the first render. | |
* | |
* componentWillLoad will only be called once. | |
*/ | |
componentWillLoad() { | |
console.log(this.stIf); | |
if (this.stIf !== undefined) { | |
this.shouldRender = new Function("return " + this.stIf)(); | |
} | |
} | |
render() { | |
if (this.shouldRender) { | |
return ( | |
<slot /> | |
); | |
} else { | |
return null; | |
} | |
} | |
} |
So what we have here is a very simple Stencil component. It takes a stIf as a property (@Prop()), that is given to the ‘st-container’ element. Then in ‘componentWillLoad()’ method we update the shouldRender attribute by evaluating the content of the stIf.
In the ‘render()’ method we check if the element and child elements should be rendered with the ‘if (this.shouldRender)’. If it should render we return the , the slot element is a placeholder for the content inside this component, the thing between the tags ”.
If you want to learn more about Stencil I suggest that you read the official Stencil getting started guide.
Publishing the component
So now when the component is done, the unit tests are working and the component is built. It’s time to publish it to the npm registry.
The first thing you need is an account at https://www.npmjs.com/
Then you need to run the command ‘npm adduser‘ and follow the instructions to login with your npm account on your machine.
Then all you need to do is run ‘npm publish‘ in your projects directory and it will be uploaded if you don’t receive any errors.
You can see the final result of my st-container over here: https://www.npmjs.com/package/stencil-container
The nice thing with Stencil components is that they by default have zero npm dependencies, pretty sweet I would say!

Conclusion
That was all I had for you this time, you can see the full source on GitHub here: https://github.com/nerdic-coder/stencil-container
Let me know what you thought of this article in the comments below and send me a pull request if you see any errors. 🙂