A <hello-message> TypeScript Web Component
A <hello-message> Web Component introduced vanilla web components
using JavaScript. This is a good start but, out of reasons, using JavaScript might not be desirable.
At the moment, there is only one component and its test and just right now might be a good moment to convert the sources to TypeScript.
Preparation
Create a new package and simply copy the src/public/js/HelloMessage.js to src/ts/HelloMessage.ts.
Please note: The component is basically left “as is” at the moment for the index.html to work without changes.
Now copy the src/public/index.html to src/public/index.html but we are not done yet. Install the required
dependencies for TypeScript, i.e.
npm install --save-dev typescript
and add the tsconfig.json configuration file
{
"compilerOptions": {
"module": "ES6",
"target": "ES6",
"alwaysStrict": true,
"strictNullChecks": true,
"noImplicitAny": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"removeComments": true
},
"include": [
"src/ts/**/*.ts"
],
"exclude": [
"node_modules"
]
}
Please note: This probably causes the IDE to show some errors in the copied sources. These can be fixed by
- adding types to the method signature of
attributeChangedCallback - add non-
nullassertion operator to accesses to the nullable fields, e.g.this.shadowRootandthis._wrapper
- add an empty
export {}statement to prevent the error message regarding the redeclaration of thetemplateblock-scoped variable.
At the moment it is not possible to open the index.html file in the browser. The TypeScript source is not
transpiled yet and there is no JavaScript version to be found at the specified location. There is another step
required.
Building
This step adds the required “Build” step to the module. To be more precise, this step will add scripts to create a directory containing all required files (a distribution) and to remove previously created distributions.
The build step is supposed to perform the following steps:
- Prepare a directory for the distribution.
- Copy static assets (i.e. the
src/public/index.htmlfile) - Compile TypeScript sources and put them into
dist/js.
The last step can be configured in the tsconfig.json file: add the outDir property to the compilerOptions object
pointing to dist/js. With shx and npm-run-all, this results in a scripts section (in the package.json) like:
{
"scripts" : {
"clean": "shx rm -rf dist",
"prepare": "shx mkdir -p dist",
"compile": "tsc",
"copy-assets": "shx cp src/public/index.html dist/",
"build" : "npm-run-all prepare compile copy-assets"
}
}
Run npm run build to create the distribution in dist/. Open dist/index.html in your browser.
Testing
Copy
- source test specification
test/HelloMessage.spec.jstosrc/test/HelloMessage.spec.tsand - the Karma configuration
karma.conf.js
and ignore the errors shown by the IDE at the moment.
Install the same dependencies as described in
Testing the <hello-message> Web Component and add the types
for jasmine and karma-typescript, e.g.
npm install --save-dev karma \
jasmine-core \
karma-jasmine \
karma-chrome-launcher \
karma-firefox-launcher \
karma-coverage \
karma-typescript \
@types/jasmine
Add the karma script to the package.json, e.g.
{
"scripts" : {
"karma" : "karma start"
}
}
The karma.conf.js needs to be updated, e.g.
module.exports = function (config) {
config.set({
frameworks: ['jasmine', 'karma-typescript'],
files: [
{ pattern: 'src/ts/**/*.ts' },
{ pattern: 'src/test/**/*.ts' }
],
preprocessors: {
'src/ts/**/*.ts' : 'karma-typescript',
'src/test/**/*.ts' : 'karma-typescript'
},
karmaTypescriptConfig: {
compilerOptions: {
module: 'ES6',
target: 'ES6'
},
exclude: [
'node_modules'
]
},
reporters: ['progress', 'karma-typescript'],
});
};
Please note:
- The
@types/jasminepackage should fix the missing symbols in the test specification. - The
karma-typescriptpreprocessor compiles tests and components to JavaScript before running the test. - The
karmaTypescriptConfigconfigures the output target.j
Test Simplification
Please note: The test from
Testing the <hello-message> Web Component
is supposed to run without any modification.
The changes sketched in the following source code snippet improve the code style of the original specification and add some type information to the fields.
describe('The <hello-message> element', () => {
let defaultElement:HTMLElement;
let defaultShadow: ShadowRoot;
let customElement: HTMLElement;
let customShadow: ShadowRoot;
beforeEach(() => {
defaultElement = document.createElement('hello-message');
defaultShadow = defaultElement.shadowRoot as ShadowRoot;
customElement = document.createElement('hello-message');
customElement.setAttribute('name', 'Friend');
customShadow = customElement.shadowRoot as ShadowRoot;
document.body.append(defaultElement, customElement);
});
afterEach(() => {
// Cleanup DOM
document.body.removeChild(defaultElement);
document.body.removeChild(customElement);
});
it('should change the message back to the default when the "name" attribute is removed.', function () {
expect(customShadow.getElementById('wrapper').innerText).toBe('Hello Friend!');
customElement.removeAttribute('name');
expect(customShadow.getElementById('wrapper').innerText).toBe('Hello World!');
});
});
Sources
As usual, the sources to the web component can be found on GitHub: https://github.com/bvfnbk/example-web-components