diff --git a/examples/with-stencil/README.md b/examples/with-stencil/README.md new file mode 100644 index 000000000000000..ac2b873e51736b8 --- /dev/null +++ b/examples/with-stencil/README.md @@ -0,0 +1,63 @@ +# Stenciljs example + +## Deploy your own + +Deploy the example using [ZEIT Now](https://zeit.co/now): + +[![Deploy with ZEIT Now](https://zeit.co/button)](https://zeit.co/new/project?template=https://github.com/zeit/next.js/tree/canary/examples/with-stencil) + +## How to use + +### Using `create-next-app` + +Execute [`create-next-app`](https://github.com/zeit/next.js/tree/canary/packages/create-next-app) with [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) or [npx](https://github.com/zkat/npx#readme) to bootstrap the example: + +```bash +npx create-next-app --example with-stencil with-stencil-app +# or +yarn create next-app --example with-stencil with-stencil-app +``` + +### Download manually + +Download the example: + +```bash +curl https://codeload.github.com/zeit/next.js/tar.gz/canary | tar -xz --strip=2 next.js-canary/examples/with-stencil +cd with-stencil +``` + +Build stencil component: + +```bash +cd packages/test-component +yarn build +``` + +Install it and run: + +```bash +yarn +yarn dev +``` + +Deploy it to the cloud with [now](https://zeit.co/now) ([download](https://zeit.co/download)): + +```bash +now +``` + +## The idea behind the example + +Stencil is a compiler that generates Web Components (more specifically, Custom Elements). Stencil combines the best concepts of the most popular frameworks into a simple build-time tool. + +In this example we have two workspaces: + +- **web-app**: A Next.js app +- **test-component**: A stencil components + +## Useful Links + +- [Documentation](https://stenciljs.com/docs/introduction) +- [yarn workspaces](https://yarnpkg.com/lang/en/docs/cli/workspace) +- [yarn workspace](https://yarnpkg.com/lang/en/docs/cli/workspaces) diff --git a/examples/with-stencil/now.json b/examples/with-stencil/now.json new file mode 100644 index 000000000000000..985a1b01d4007ef --- /dev/null +++ b/examples/with-stencil/now.json @@ -0,0 +1,5 @@ +{ + "version": 2, + "builds": [{ "src": "packages/web-app/package.json", "use": "@now/next" }], + "routes": [{ "src": "(.*)", "dest": "packages/web-app$1", "continue": true }] +} diff --git a/examples/with-stencil/package.json b/examples/with-stencil/package.json new file mode 100644 index 000000000000000..60a794a3f006d7a --- /dev/null +++ b/examples/with-stencil/package.json @@ -0,0 +1,11 @@ +{ + "private": true, + "workspaces": [ + "packages/*" + ], + "scripts": { + "dev": "yarn --cwd packages/web-app dev", + "build": "yarn --cwd packages/web-app build", + "start": "yarn --cwd packages/web-app start" + } +} diff --git a/examples/with-stencil/packages/test-component/.gitignore b/examples/with-stencil/packages/test-component/.gitignore new file mode 100644 index 000000000000000..c3ea58a61b71b10 --- /dev/null +++ b/examples/with-stencil/packages/test-component/.gitignore @@ -0,0 +1,26 @@ +dist/ +www/ +loader/ + +*~ +*.sw[mnpcod] +*.log +*.lock +*.tmp +*.tmp.* +log.txt +*.sublime-project +*.sublime-workspace + +.stencil/ +.idea/ +.vscode/ +.sass-cache/ +.versions/ +node_modules/ +$RECYCLE.BIN/ + +.DS_Store +Thumbs.db +UserInterfaceState.xcuserstate +.env diff --git a/examples/with-stencil/packages/test-component/LICENSE b/examples/with-stencil/packages/test-component/LICENSE new file mode 100644 index 000000000000000..b442934b7df2ca7 --- /dev/null +++ b/examples/with-stencil/packages/test-component/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/examples/with-stencil/packages/test-component/package.json b/examples/with-stencil/packages/test-component/package.json new file mode 100644 index 000000000000000..5cf67baf0b95d7c --- /dev/null +++ b/examples/with-stencil/packages/test-component/package.json @@ -0,0 +1,28 @@ +{ + "name": "test-component", + "version": "0.0.1", + "description": "Stencil Component Starter", + "main": "dist/index.js", + "module": "dist/index.mjs", + "es2015": "dist/esm/index.mjs", + "es2017": "dist/esm/index.mjs", + "types": "dist/types/index.d.ts", + "collection": "dist/collection/collection-manifest.json", + "collection:main": "dist/collection/index.js", + "unpkg": "dist/test-component/test-component.js", + "files": [ + "dist/", + "loader/" + ], + "scripts": { + "build": "stencil build --docs", + "start": "stencil build --dev --watch --serve", + "test": "stencil test --spec --e2e", + "test.watch": "stencil test --spec --e2e --watchAll", + "generate": "stencil generate" + }, + "devDependencies": { + "@stencil/core": "^1.8.4" + }, + "license": "MIT" +} diff --git a/examples/with-stencil/packages/test-component/readme.md b/examples/with-stencil/packages/test-component/readme.md new file mode 100644 index 000000000000000..438a2241ee2a7cd --- /dev/null +++ b/examples/with-stencil/packages/test-component/readme.md @@ -0,0 +1,72 @@ +![Built With Stencil](https://img.shields.io/badge/-Built%20With%20Stencil-16161d.svg?logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDE5LjIuMSwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCA1MTIgNTEyIiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCA1MTIgNTEyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI%2BCjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI%2BCgkuc3Qwe2ZpbGw6I0ZGRkZGRjt9Cjwvc3R5bGU%2BCjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik00MjQuNywzNzMuOWMwLDM3LjYtNTUuMSw2OC42LTkyLjcsNjguNkgxODAuNGMtMzcuOSwwLTkyLjctMzAuNy05Mi43LTY4LjZ2LTMuNmgzMzYuOVYzNzMuOXoiLz4KPHBhdGggY2xhc3M9InN0MCIgZD0iTTQyNC43LDI5Mi4xSDE4MC40Yy0zNy42LDAtOTIuNy0zMS05Mi43LTY4LjZ2LTMuNkgzMzJjMzcuNiwwLDkyLjcsMzEsOTIuNyw2OC42VjI5Mi4xeiIvPgo8cGF0aCBjbGFzcz0ic3QwIiBkPSJNNDI0LjcsMTQxLjdIODcuN3YtMy42YzAtMzcuNiw1NC44LTY4LjYsOTIuNy02OC42SDMzMmMzNy45LDAsOTIuNywzMC43LDkyLjcsNjguNlYxNDEuN3oiLz4KPC9zdmc%2BCg%3D%3D&colorA=16161d&style=flat-square) + +# Stencil Component Starter + +This is a starter project for building a standalone Web Component using Stencil. + +Stencil is also great for building entire apps. For that, use the [stencil-app-starter](https://github.com/ionic-team/stencil-app-starter) instead. + +# Stencil + +Stencil is a compiler for building fast web apps using Web Components. + +Stencil combines the best concepts of the most popular frontend frameworks into a compile-time rather than run-time tool. Stencil takes TypeScript, JSX, a tiny virtual DOM layer, efficient one-way data binding, an asynchronous rendering pipeline (similar to React Fiber), and lazy-loading out of the box, and generates 100% standards-based Web Components that run in any browser supporting the Custom Elements v1 spec. + +Stencil components are just Web Components, so they work in any major framework or with no framework at all. + +## Getting Started + +To start building a new web component using Stencil, clone this repo to a new directory: + +```bash +git clone https://github.com/ionic-team/stencil-component-starter.git my-component +cd my-component +git remote rm origin +``` + +and run: + +```bash +npm install +npm start +``` + +To build the component for production, run: + +```bash +npm run build +``` + +To run the unit tests for the components, run: + +```bash +npm test +``` + +Need help? Check out our docs [here](https://stenciljs.com/docs/my-first-component). + +## Naming Components + +When creating new component tags, we recommend _not_ using `stencil` in the component name (ex: ``). This is because the generated component has little to nothing to do with Stencil; it's just a web component! + +Instead, use a prefix that fits your company or any name for a group of related components. For example, all of the Ionic generated web components use the prefix `ion`. + +## Using this component + +### Script tag + +- [Publish to NPM](https://docs.npmjs.com/getting-started/publishing-npm-packages) +- Put a script tag similar to this `` in the head of your index.html +- Then you can use the element anywhere in your template, JSX, html etc + +### Node Modules + +- Run `npm install my-component --save` +- Put a script tag similar to this `` in the head of your index.html +- Then you can use the element anywhere in your template, JSX, html etc + +### In a stencil-starter app + +- Run `npm install my-component --save` +- Add an import to the npm packages `import my-component;` +- Then you can use the element anywhere in your template, JSX, html etc diff --git a/examples/with-stencil/packages/test-component/src/components.d.ts b/examples/with-stencil/packages/test-component/src/components.d.ts new file mode 100644 index 000000000000000..3c8b5104e87c921 --- /dev/null +++ b/examples/with-stencil/packages/test-component/src/components.d.ts @@ -0,0 +1,70 @@ +/* eslint-disable */ +/* tslint:disable */ +/** + * This is an autogenerated file created by the Stencil compiler. + * It contains typing information for all components that exist in this project. + */ + +import { HTMLStencilElement, JSXBase } from '@stencil/core/internal' + +export namespace Components { + interface MyComponent { + /** + * The first name + */ + first: string + /** + * The last name + */ + last: string + /** + * The middle name + */ + middle: string + } +} + +declare global { + interface HTMLMyComponentElement + extends Components.MyComponent, + HTMLStencilElement {} + var HTMLMyComponentElement: { + prototype: HTMLMyComponentElement + new (): HTMLMyComponentElement + } + interface HTMLElementTagNameMap { + 'my-component': HTMLMyComponentElement + } +} + +declare namespace LocalJSX { + interface MyComponent { + /** + * The first name + */ + first?: string + /** + * The last name + */ + last?: string + /** + * The middle name + */ + middle?: string + } + + interface IntrinsicElements { + 'my-component': MyComponent + } +} + +export { LocalJSX as JSX } + +declare module '@stencil/core' { + export namespace JSX { + interface IntrinsicElements { + 'my-component': LocalJSX.MyComponent & + JSXBase.HTMLAttributes + } + } +} diff --git a/examples/with-stencil/packages/test-component/src/components/my-component/my-component.css b/examples/with-stencil/packages/test-component/src/components/my-component/my-component.css new file mode 100644 index 000000000000000..e69de29bb2d1d64 diff --git a/examples/with-stencil/packages/test-component/src/components/my-component/my-component.e2e.ts b/examples/with-stencil/packages/test-component/src/components/my-component/my-component.e2e.ts new file mode 100644 index 000000000000000..2fcd9f1da9bf16a --- /dev/null +++ b/examples/with-stencil/packages/test-component/src/components/my-component/my-component.e2e.ts @@ -0,0 +1,32 @@ +import { newE2EPage } from '@stencil/core/testing' + +describe('my-component', () => { + it('renders', async () => { + const page = await newE2EPage() + + await page.setContent('') + const element = await page.find('my-component') + expect(element).toHaveClass('hydrated') + }) + + it('renders changes to the name data', async () => { + const page = await newE2EPage() + + await page.setContent('') + const component = await page.find('my-component') + const element = await page.find('my-component >>> div') + expect(element.textContent).toEqual(`Hello, World! I'm `) + + component.setProperty('first', 'James') + await page.waitForChanges() + expect(element.textContent).toEqual(`Hello, World! I'm James`) + + component.setProperty('last', 'Quincy') + await page.waitForChanges() + expect(element.textContent).toEqual(`Hello, World! I'm James Quincy`) + + component.setProperty('middle', 'Earl') + await page.waitForChanges() + expect(element.textContent).toEqual(`Hello, World! I'm James Earl Quincy`) + }) +}) diff --git a/examples/with-stencil/packages/test-component/src/components/my-component/my-component.tsx b/examples/with-stencil/packages/test-component/src/components/my-component/my-component.tsx new file mode 100644 index 000000000000000..5fdf96be44d2485 --- /dev/null +++ b/examples/with-stencil/packages/test-component/src/components/my-component/my-component.tsx @@ -0,0 +1,33 @@ +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import { Component, Prop, h } from '@stencil/core' +import { format } from '../../utils/utils' + +@Component({ + tag: 'my-component', + styleUrl: 'my-component.css', + shadow: true, +}) +export class MyComponent { + /** + * The first name + */ + @Prop() first: string + + /** + * The middle name + */ + @Prop() middle: string + + /** + * The last name + */ + @Prop() last: string + + private getText(): string { + return format(this.first, this.middle, this.last) + } + + render() { + return
Hello, World! I'm {this.getText()}
+ } +} diff --git a/examples/with-stencil/packages/test-component/src/components/my-component/readme.md b/examples/with-stencil/packages/test-component/src/components/my-component/readme.md new file mode 100644 index 000000000000000..b927eea947707ff --- /dev/null +++ b/examples/with-stencil/packages/test-component/src/components/my-component/readme.md @@ -0,0 +1,15 @@ +# my-component + + + +## Properties + +| Property | Attribute | Description | Type | Default | +| -------- | --------- | --------------- | -------- | ----------- | +| `first` | `first` | The first name | `string` | `undefined` | +| `last` | `last` | The last name | `string` | `undefined` | +| `middle` | `middle` | The middle name | `string` | `undefined` | + +--- + +_Built with [StencilJS](https://stenciljs.com/)_ diff --git a/examples/with-stencil/packages/test-component/src/index.html b/examples/with-stencil/packages/test-component/src/index.html new file mode 100644 index 000000000000000..a8e534f2daea78c --- /dev/null +++ b/examples/with-stencil/packages/test-component/src/index.html @@ -0,0 +1,20 @@ + + + + + + Stencil Component Starter + + + + + + + + diff --git a/examples/with-stencil/packages/test-component/src/index.ts b/examples/with-stencil/packages/test-component/src/index.ts new file mode 100644 index 000000000000000..cb64ac1b52aada9 --- /dev/null +++ b/examples/with-stencil/packages/test-component/src/index.ts @@ -0,0 +1 @@ +export * from './components' diff --git a/examples/with-stencil/packages/test-component/src/utils/utils.spec.ts b/examples/with-stencil/packages/test-component/src/utils/utils.spec.ts new file mode 100644 index 000000000000000..db69728c03eb222 --- /dev/null +++ b/examples/with-stencil/packages/test-component/src/utils/utils.spec.ts @@ -0,0 +1,21 @@ +import { format } from './utils' + +describe('format', () => { + it('returns empty string for no names defined', () => { + expect(format(undefined, undefined, undefined)).toEqual('') + }) + + it('formats just first names', () => { + expect(format('Joseph', undefined, undefined)).toEqual('Joseph') + }) + + it('formats first and last names', () => { + expect(format('Joseph', undefined, 'Publique')).toEqual('Joseph Publique') + }) + + it('formats first, middle and last names', () => { + expect(format('Joseph', 'Quincy', 'Publique')).toEqual( + 'Joseph Quincy Publique' + ) + }) +}) diff --git a/examples/with-stencil/packages/test-component/src/utils/utils.ts b/examples/with-stencil/packages/test-component/src/utils/utils.ts new file mode 100644 index 000000000000000..15d205fd1a74c8b --- /dev/null +++ b/examples/with-stencil/packages/test-component/src/utils/utils.ts @@ -0,0 +1,3 @@ +export function format(first: string, middle: string, last: string): string { + return (first || '') + (middle ? ` ${middle}` : '') + (last ? ` ${last}` : '') +} diff --git a/examples/with-stencil/packages/test-component/stencil.config.ts b/examples/with-stencil/packages/test-component/stencil.config.ts new file mode 100644 index 000000000000000..19dff0f5352800d --- /dev/null +++ b/examples/with-stencil/packages/test-component/stencil.config.ts @@ -0,0 +1,18 @@ +import { Config } from '@stencil/core' + +export const config: Config = { + namespace: 'test-component', + outputTargets: [ + { + type: 'dist', + esmLoaderPath: '../loader', + }, + { + type: 'docs-readme', + }, + { + type: 'www', + serviceWorker: null, // disable service workers + }, + ], +} diff --git a/examples/with-stencil/packages/test-component/tsconfig.json b/examples/with-stencil/packages/test-component/tsconfig.json new file mode 100644 index 000000000000000..1ba949095d2656e --- /dev/null +++ b/examples/with-stencil/packages/test-component/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "allowSyntheticDefaultImports": true, + "allowUnreachableCode": false, + "declaration": false, + "experimentalDecorators": true, + "lib": ["dom", "es2017"], + "moduleResolution": "node", + "module": "esnext", + "target": "es2017", + "noUnusedLocals": true, + "noUnusedParameters": true, + "jsx": "react", + "jsxFactory": "h" + }, + "include": ["src", "types/jsx.d.ts"], + "exclude": ["node_modules"] +} diff --git a/examples/with-stencil/packages/web-app/package.json b/examples/with-stencil/packages/web-app/package.json new file mode 100644 index 000000000000000..83f9086886282c2 --- /dev/null +++ b/examples/with-stencil/packages/web-app/package.json @@ -0,0 +1,15 @@ +{ + "name": "web-app", + "version": "1.0.0", + "scripts": { + "dev": "next", + "build": "next build", + "start": "next start" + }, + "dependencies": { + "next": "latest", + "react": "^16.8.3", + "react-dom": "^16.8.3" + }, + "license": "ISC" +} diff --git a/examples/with-stencil/packages/web-app/pages/_app.js b/examples/with-stencil/packages/web-app/pages/_app.js new file mode 100644 index 000000000000000..6ff0a5f5d41bce5 --- /dev/null +++ b/examples/with-stencil/packages/web-app/pages/_app.js @@ -0,0 +1,17 @@ +import React from 'react' +import App from 'next/app' + +import { applyPolyfills, defineCustomElements } from 'test-component/loader' + +export default class MyApp extends App { + componentDidMount() { + applyPolyfills().then(() => { + defineCustomElements(window) + }) + } + + render() { + const { Component, pageProps } = this.props + return + } +} diff --git a/examples/with-stencil/packages/web-app/pages/index.js b/examples/with-stencil/packages/web-app/pages/index.js new file mode 100644 index 000000000000000..a0995f106401b10 --- /dev/null +++ b/examples/with-stencil/packages/web-app/pages/index.js @@ -0,0 +1,5 @@ +export default () => ( +
+ +
+)