diff --git a/browser.d.ts b/browser.d.ts new file mode 100644 index 00000000..1a2a1997 --- /dev/null +++ b/browser.d.ts @@ -0,0 +1,3 @@ +interface ImportMeta { + url: string; +} diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 1f65e22a..99ceeace 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -15,6 +15,7 @@ const sidebar = [ ['/developing/', 'Getting started'], '/developing/owc-dev-server', ['/developing/create', 'Generators'], + '/developing/types', ], }, { diff --git a/docs/developing/types.md b/docs/developing/types.md new file mode 100644 index 00000000..024c6288 --- /dev/null +++ b/docs/developing/types.md @@ -0,0 +1,118 @@ +# Types + +## Types are good + +> The following information is a quote from [Type Safe JavaScript with JSDoc](https://medium.com/@trukrs/type-safe-javascript-with-jsdoc-7a2a63209b76) + +Types provide valuable information about the nature of code, and help identify typos, enable refactoring, etc. Most editors these days provide some kind of IntelliSense based on type information gleaned from the code. My favorite is Visual Studio Code. With proper type information it can give you symbol defintions on hover, code completion, symbol renaming across files, etc. + +### Benefits of Types +1. Early detection of type errors +2. Better code analysis +3. Improved IDE support +4. Promotes dependable refactoring +5. Improves code readability +6. Provides useful IntelliSense while coding +7. These benefits are provided by using TypeScript, Flow and even JSDoc comments. + +## Open Web Components Types + +For most of our products we do offer types via JSDocs. +In order to utalize them you will need to add something to your setup. + +### Setup for JavaScript + +In order to get type linting in a JavaScript project using VS Code all you need is to add a `jsconfig.json`. + +```json +{ + "compilerOptions": { + "target": "esnext", + "moduleResolution": "node", + "lib": ["es2017", "dom"], + "checkJs": true, + "strictNullChecks": true, + "noImplicitThis": true, + "alwaysStrict": true, + "esModuleInterop": true + }, + "exclude": [ + "node_modules", + ] +} +``` + +### Setup for TypeScript + +If you wish to use our typings in TypeScript you will to do a little more. + +You will need to add to this to your `tsconfig.json`. +```json +{ + "compilerOptions": { + "allowJs": true, + "checkJs": true, + }, + "include": [ + "node_modules/@open-wc/**/*.js" + ], + "exclude": [ + "node_modules/!(@open-wc)", + ] +} +``` + +e.g. we need to include the js files from @open-wc and you can not have it in an exclude. + +However as `allowJs` prevents you from generating definition files for your own typescript files ([issue 7546](https://github.com/Microsoft/TypeScript/issues/7546)) you probably want to have an alternative config `tsconfig.build.json` for that. + + +```json +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "allowJs": false, + "checkJs": false + } +} +``` + +You can then use it like so in you `package.json` +```json +{ + "scripts": { + "lint:types": "tsc", + "build": "tsc -p tsconfig.build.json" + } +} +``` + +That way +- `tsconfig.json` will be used by the language server (in VS code) +- `tsconfig.build.json` will be used to build your typescript project (including definition files) + + +Example how a full config might look like +```json +{ + "compilerOptions": { + "target": "esnext", + "module": "esnext", + "moduleResolution": "node", + "lib": ["es2017", "dom"], + "allowJs": true, + "checkJs": true, + "noEmit": true, + "strict": false, + "noImplicitThis": true, + "alwaysStrict": true, + "esModuleInterop": true, + }, + "include": [ + "node_modules/@open-wc/**/*.js" + ], + "exclude": [ + "node_modules/!(@open-wc)", + ] +} +``` diff --git a/package.json b/package.json index e75e426e..9aa81086 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "lint": "run-p lint:*", "lint:eslint": "eslint --ext .js,.html .", "lint:prettier": "prettier \"**/*.js\" --list-different || (echo '↑↑ these files are not prettier formatted ↑↑' && exit 1)", + "lint:types": "tsc", "format": "npm run format:eslint && npm run format:prettier", "format:eslint": "eslint --ext .js,.html . --fix", "format:prettier": "prettier \"**/*.js\" --write", @@ -35,6 +36,7 @@ "@commitlint/config-lerna-scopes": "7.2.1", "@open-wc/testing-karma": "file:./packages/testing-karma", "@open-wc/testing-karma-bs": "file:./packages/testing-karma-bs", + "@types/mocha": "^5.0.0", "@vuepress/plugin-google-analytics": "^1.0.0-alpha.30", "eslint": "^5.13.0", "eslint-config-prettier": "^3.3.0", @@ -43,6 +45,7 @@ "lint-staged": "^8.1.0", "npm-run-all": "4.1.3", "prettier": "^1.15.0", + "typescript": "^3.3.3333", "vuepress": "^1.0.0-alpha.30", "webpack-merge": "^4.1.5" } diff --git a/packages/building-rollup/src/dynamic-import-polyfill.js b/packages/building-rollup/src/dynamic-import-polyfill.js index c3f1dc86..19320522 100644 --- a/packages/building-rollup/src/dynamic-import-polyfill.js +++ b/packages/building-rollup/src/dynamic-import-polyfill.js @@ -1,9 +1,20 @@ +/** + * Changes a relative URL to an absolute URL. + * + * @example + * toAbsoluteURL('hoge.html') // http://example.com/hoge.html + * + * @param {string} url Relative URL + * @returns {string} Absolute URL + */ function toAbsoluteURL(url) { const a = document.createElement('a'); a.setAttribute('href', url); // + // @ts-ignore return a.cloneNode(false).href; // -> "http://example.com/hoge.html" } +// @ts-ignore window.importModule = function importModule(url) { return new Promise((resolve, reject) => { const vector = `$importModule$${Math.random() @@ -18,7 +29,7 @@ window.importModule = function importModule(url) { URL.revokeObjectURL(script.src); script.src = ''; }; - script.defer = 'defer'; + script.defer = true; script.type = 'module'; script.onerror = () => { reject(new Error(`Failed to import: ${url}`)); diff --git a/packages/building-webpack/demo/ts-babel/babel-demo-app.ts b/packages/building-webpack/demo/ts-babel/babel-demo-app.ts new file mode 100644 index 00000000..48dc1fe6 --- /dev/null +++ b/packages/building-webpack/demo/ts-babel/babel-demo-app.ts @@ -0,0 +1,15 @@ +async function babelMessage(nr: number): Promise { + return `Hello typescript! ${nr * 2}`; +} + +class BabelDemoApp extends HTMLElement { + async connectedCallback() { + const msg = await babelMessage(2); + + this.innerHTML = ` +

${msg}

+ `; + } +} + +customElements.define('babel-demo-app', BabelDemoApp); diff --git a/packages/building-webpack/demo/ts-babel/demo-app.ts b/packages/building-webpack/demo/ts-babel/demo-app.ts deleted file mode 100644 index af881e3c..00000000 --- a/packages/building-webpack/demo/ts-babel/demo-app.ts +++ /dev/null @@ -1,15 +0,0 @@ -async function message(nr: number): Promise { - return `Hello typescript! ${nr * 2}`; -} - -class DemoApp extends HTMLElement { - async connectedCallback() { - const msg = await message(2); - - this.innerHTML = ` -

${msg}

- `; - } -} - -customElements.define('demo-app', DemoApp); \ No newline at end of file diff --git a/packages/building-webpack/demo/ts-babel/index.html b/packages/building-webpack/demo/ts-babel/index.html index db977ad9..8eb7b641 100644 --- a/packages/building-webpack/demo/ts-babel/index.html +++ b/packages/building-webpack/demo/ts-babel/index.html @@ -12,7 +12,7 @@

Static content in index.html is preserved

- + diff --git a/packages/building-webpack/modern-and-legacy-config.js b/packages/building-webpack/modern-and-legacy-config.js index 92ca6f68..f8ccf944 100644 --- a/packages/building-webpack/modern-and-legacy-config.js +++ b/packages/building-webpack/modern-and-legacy-config.js @@ -93,6 +93,7 @@ function createConfig(options, legacy) { }, plugins: [ + // @ts-ignore !development && new CleanWebpackPlugin(), new HtmlWebpackPlugin({ diff --git a/packages/building-webpack/modern-config.js b/packages/building-webpack/modern-config.js index eeb762fb..25f86078 100644 --- a/packages/building-webpack/modern-config.js +++ b/packages/building-webpack/modern-config.js @@ -86,6 +86,7 @@ module.exports = userOptions => { }, plugins: [ + // @ts-ignore !development && new CleanWebpackPlugin(), new HtmlWebpackPlugin({ diff --git a/packages/building-webpack/src/modern-web-webpack-plugin.js b/packages/building-webpack/src/modern-web-webpack-plugin.js index 3b1a2840..fd0d7f9d 100644 --- a/packages/building-webpack/src/modern-web-webpack-plugin.js +++ b/packages/building-webpack/src/modern-web-webpack-plugin.js @@ -1,3 +1,4 @@ +// @ts-nocheck /* eslint-disable no-param-reassign */ const path = require('path'); const CopyWebpackPlugin = require('copy-webpack-plugin'); diff --git a/packages/chai-dom-equals/chai-dom-equals.d.ts b/packages/chai-dom-equals/chai-dom-equals.d.ts deleted file mode 100644 index b1ce1b42..00000000 --- a/packages/chai-dom-equals/chai-dom-equals.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -import * as Chai from 'chai' -export function getOuterHtml(el: Element): string; -export function getCleanedShadowDom(el: Element): string; -export function chaiDomEquals(chai: Chai, utils: any): undefined; diff --git a/packages/chai-dom-equals/chai-dom-equals.js b/packages/chai-dom-equals/chai-dom-equals.js index 2b217658..45251544 100644 --- a/packages/chai-dom-equals/chai-dom-equals.js +++ b/packages/chai-dom-equals/chai-dom-equals.js @@ -3,8 +3,12 @@ import { getDiffableSemanticHTML } from '@open-wc/semantic-dom-diff'; /** * el.outerHTML is not polyfilled so we need to recreate the tag + attributes and * combine it with el.innerHTML. + * + * @param {Element} el Element you want to get the out Html from + * @returns {String} outer html */ export const getOuterHtml = el => { + // @ts-ignore if (window.ShadyCSS && window.ShadyCSS.nativeShadow === false) { const tagName = el.tagName.toLowerCase(); let attributes = ' '; @@ -21,8 +25,12 @@ export const getOuterHtml = el => { /** * For comparision we do not need the style scoping classes on polyfilled browsers * Rather naive approach for now - probably need to improve once we have failing cases. + * + * @param {Element} el Element you want to get the cleaned shadow dom + * @returns {String} cleaned shadow dom */ export const getCleanedShadowDom = el => { + // @ts-ignore if (window.ShadyCSS && window.ShadyCSS.nativeShadow === false) { const tagName = el.tagName.toLowerCase(); const regexTagName = new RegExp(tagName, 'g'); @@ -36,16 +44,27 @@ export const getCleanedShadowDom = el => { return el.shadowRoot.innerHTML; }; +/** + * Setup the + * + * Note: can not be an arrow function as it gets rebound + * + * @param {any} chai + * @param {any} utils + */ export const chaiDomEquals = (chai, utils) => { - // can not be an arrow function as it gets rebound chai.Assertion.addProperty('dom', function dom() { + // @ts-ignore new chai.Assertion(this._obj.nodeType).to.equal(1); + // @ts-ignore utils.flag(this, 'dom', true); }); // can not be an arrow function as it gets rebound chai.Assertion.addProperty('shadowDom', function shadowDom() { + // @ts-ignore new chai.Assertion(this._obj.nodeType).to.equal(1); + // @ts-ignore utils.flag(this, 'shadowDom', true); }); @@ -53,15 +72,19 @@ export const chaiDomEquals = (chai, utils) => { // TODO: this is here for backwards compatibility, removal will be // a breaking change chai.Assertion.addProperty('semantically', function shadowDom() { + // @ts-ignore new chai.Assertion(this._obj.nodeType).to.equal(1); + // @ts-ignore utils.flag(this, 'semantically', true); }); // can not be an arrow function as it gets rebound // prettier-ignore const domEquals = _super => function handleDom(value, ...args) { + // @ts-ignore if (utils.flag(this, 'dom')) { const expectedHTML = getDiffableSemanticHTML(value); + // @ts-ignore const actualHTML = getDiffableSemanticHTML(getOuterHtml(this._obj)); // use chai's built-in string comparison, log the updated snapshot on error @@ -76,8 +99,10 @@ export const chaiDomEquals = (chai, utils) => { throw error; } + // @ts-ignore } else if (utils.flag(this, 'shadowDom')) { const expectedHTML = getDiffableSemanticHTML(value); + // @ts-ignore const actualHTML = getDiffableSemanticHTML(getCleanedShadowDom(this._obj)); // use chai's built-in string comparison, log the updated snapshot on error @@ -93,6 +118,7 @@ export const chaiDomEquals = (chai, utils) => { } } else { + // @ts-ignore _super.apply(this, [value, ...args]); } }; diff --git a/packages/chai-dom-equals/index.d.ts b/packages/chai-dom-equals/index.d.ts deleted file mode 100644 index 087c1e2f..00000000 --- a/packages/chai-dom-equals/index.d.ts +++ /dev/null @@ -1 +0,0 @@ -export { getOuterHtml, getCleanedShadowDom, chaiDomEquals } from './chai-dom-equals'; diff --git a/packages/demoing-storybook/demo/my-el.js b/packages/demoing-storybook/demo/my-el.js index 1ae464e2..087fdc14 100644 --- a/packages/demoing-storybook/demo/my-el.js +++ b/packages/demoing-storybook/demo/my-el.js @@ -21,6 +21,8 @@ class Base extends LitElement { } } +// TODO: check how to allow extending with added properties with LitElement +// @ts-ignore export class MyEl extends Base { static get styles() { return css` @@ -47,6 +49,7 @@ export class MyEl extends Base { constructor() { super(); + this.locked = false; this.header = 'Default Header'; this.headerColor = '#ff0000'; this.disabled = false; diff --git a/packages/demoing-storybook/demo/stories/index.stories.js b/packages/demoing-storybook/demo/stories/index.stories.js index dc3497b0..2f33acc0 100644 --- a/packages/demoing-storybook/demo/stories/index.stories.js +++ b/packages/demoing-storybook/demo/stories/index.stories.js @@ -10,6 +10,7 @@ import { } from '../../index.js'; import { MyEl } from '../my-el.js'; +// @ts-ignore import readme from '../README.md'; addParameters({ diff --git a/packages/demoing-storybook/index.d.ts b/packages/demoing-storybook/index.d.ts deleted file mode 100644 index b4195525..00000000 --- a/packages/demoing-storybook/index.d.ts +++ /dev/null @@ -1,23 +0,0 @@ -export { html } from 'lit-html'; - -// NB: @types/storybook__polymer doesn't yet exist -// export { storiesOf, addParameters } from '@storybook/polymer'; -export { action } from '@storybook/addon-actions'; -export { linkTo } from '@storybook/addon-links'; -// NB: @types/storybook__addon-backgrounds is 2 major versions behind -// export { withBackgrounds } from '@storybook/addon-backgrounds'; -export { withNotes } from '@storybook/addon-notes'; - -export { - withKnobs, - text, - button, - number, - select, - date, - color, - array, - boolean, -} from '@storybook/addon-knobs'; - -export { withClassPropertiesKnobs } from './withClassPropertiesKnobs.js'; diff --git a/packages/demoing-storybook/package.json b/packages/demoing-storybook/package.json index 68a33d16..4e70553b 100644 --- a/packages/demoing-storybook/package.json +++ b/packages/demoing-storybook/package.json @@ -37,7 +37,6 @@ "@storybook/polymer": "^5.0.0", "@types/storybook__addon-actions": "^3.4.1", "@types/storybook__addon-backgrounds": "^3.2.1", - "@types/storybook__addon-knobs": "^3.4.1", "@types/storybook__addon-links": "^3.3.3", "@types/storybook__addon-notes": "^3.3.3", "@webcomponents/webcomponentsjs": "^2.2.0", diff --git a/packages/demoing-storybook/withClassPropertiesKnobs.js b/packages/demoing-storybook/withClassPropertiesKnobs.js index 1fc15328..a1475b5f 100644 --- a/packages/demoing-storybook/withClassPropertiesKnobs.js +++ b/packages/demoing-storybook/withClassPropertiesKnobs.js @@ -2,6 +2,12 @@ import { text, number, date, object, array, boolean } from '@storybook/addon-kno // eslint-disable-next-line import/no-extraneous-dependencies import { render } from 'lit-html'; +/** + * @typedef {Object} OptionsWithClassPropertiesKnobs + * @property {function} [overrides] Override knobs for specific properties + * @property {import('lit-html').TemplateResult} [template] Your template if you need properties or children set + */ + /** * @example * class MyEl extends LitElement { ... } @@ -18,6 +24,8 @@ import { render } from 'lit-html'; * { key: 'locked', group: 'Security' }, // change group of an default Element property * ])); * }); + * @param {any} Klass The class (not instance) you want the knobs for + * @param {OptionsWithClassPropertiesKnobs} Options Define overrides and a template if neeed */ export function withClassPropertiesKnobs(Klass, { overrides: overrideFunction, template } = {}) { let el; diff --git a/packages/owc-dev-server/owc-dev-server.js b/packages/owc-dev-server/owc-dev-server.js index d73e9b77..2ebeca6e 100755 --- a/packages/owc-dev-server/owc-dev-server.js +++ b/packages/owc-dev-server/owc-dev-server.js @@ -80,7 +80,7 @@ if ('help' in options) { }, ]), ); - return; + process.exit(); } // open if the open option is given, even when there were no arguments diff --git a/packages/polyfills-loader/polyfills-loader.js b/packages/polyfills-loader/polyfills-loader.js index a637a9de..9110e9bc 100644 --- a/packages/polyfills-loader/polyfills-loader.js +++ b/packages/polyfills-loader/polyfills-loader.js @@ -3,7 +3,6 @@ * * Adapted to not load language polyfills and use dynamic imports */ - function needsTemplatePolyfill() { // no real