Add stencil frontend
This commit is contained in:
parent
104dcf6acd
commit
5719891df8
|
@ -0,0 +1,15 @@
|
|||
# http://editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
insert_final_newline = false
|
||||
trim_trailing_whitespace = false
|
|
@ -0,0 +1,26 @@
|
|||
dist/
|
||||
!www/favicon.ico
|
||||
www/
|
||||
|
||||
*~
|
||||
*.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
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"arrowParens": "avoid",
|
||||
"bracketSpacing": true,
|
||||
"jsxBracketSameLine": false,
|
||||
"jsxSingleQuote": false,
|
||||
"quoteProps": "consistent",
|
||||
"printWidth": 180,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "all",
|
||||
"useTabs": false
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2019
|
||||
|
||||
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.
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"name": "clndr",
|
||||
"private": true,
|
||||
"version": "0.0.1",
|
||||
"description": "Stencil App Starter",
|
||||
"scripts": {
|
||||
"build": "stencil build",
|
||||
"start": "stencil build --dev --watch --serve",
|
||||
"test": "stencil test --spec --e2e",
|
||||
"test.watch": "stencil test --spec --e2e --watchAll",
|
||||
"generate": "stencil generate"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@stencil-community/router": "^1.0.2",
|
||||
"@stencil/core": "2.13.0",
|
||||
"@types/jest": "^27.0.3",
|
||||
"jest": "^27.4.5",
|
||||
"jest-cli": "^27.4.5",
|
||||
"puppeteer": "^10.0.0"
|
||||
},
|
||||
"license": "MIT"
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
# Stencil App Starter
|
||||
|
||||
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. In many cases, Stencil can be used as a drop in replacement for traditional frontend frameworks given the capabilities now available in the browser, though using it as such is certainly not required.
|
||||
|
||||
Stencil also enables a number of key capabilities on top of Web Components, in particular Server Side Rendering (SSR) without the need to run a headless browser, pre-rendering, and objects-as-properties (instead of just strings).
|
||||
|
||||
## Getting Started
|
||||
|
||||
To start a new project using Stencil, clone this repo to a new directory:
|
||||
|
||||
```bash
|
||||
npm init stencil app
|
||||
```
|
||||
|
||||
and run:
|
||||
|
||||
```bash
|
||||
npm start
|
||||
```
|
||||
|
||||
To build the app for production, run:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
To run the unit tests once, run:
|
||||
|
||||
```
|
||||
npm test
|
||||
```
|
||||
|
||||
To run the unit tests and watch for file changes during development, run:
|
||||
|
||||
```
|
||||
npm run test.watch
|
||||
```
|
Binary file not shown.
After Width: | Height: | Size: 549 B |
Binary file not shown.
After Width: | Height: | Size: 6.6 KiB |
|
@ -0,0 +1,66 @@
|
|||
/* 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";
|
||||
import { MatchResults } from "@stencil-community/router";
|
||||
export namespace Components {
|
||||
interface AppHome {
|
||||
}
|
||||
interface AppProfile {
|
||||
"match": MatchResults;
|
||||
}
|
||||
interface AppRoot {
|
||||
}
|
||||
}
|
||||
declare global {
|
||||
interface HTMLAppHomeElement extends Components.AppHome, HTMLStencilElement {
|
||||
}
|
||||
var HTMLAppHomeElement: {
|
||||
prototype: HTMLAppHomeElement;
|
||||
new (): HTMLAppHomeElement;
|
||||
};
|
||||
interface HTMLAppProfileElement extends Components.AppProfile, HTMLStencilElement {
|
||||
}
|
||||
var HTMLAppProfileElement: {
|
||||
prototype: HTMLAppProfileElement;
|
||||
new (): HTMLAppProfileElement;
|
||||
};
|
||||
interface HTMLAppRootElement extends Components.AppRoot, HTMLStencilElement {
|
||||
}
|
||||
var HTMLAppRootElement: {
|
||||
prototype: HTMLAppRootElement;
|
||||
new (): HTMLAppRootElement;
|
||||
};
|
||||
interface HTMLElementTagNameMap {
|
||||
"app-home": HTMLAppHomeElement;
|
||||
"app-profile": HTMLAppProfileElement;
|
||||
"app-root": HTMLAppRootElement;
|
||||
}
|
||||
}
|
||||
declare namespace LocalJSX {
|
||||
interface AppHome {
|
||||
}
|
||||
interface AppProfile {
|
||||
"match"?: MatchResults;
|
||||
}
|
||||
interface AppRoot {
|
||||
}
|
||||
interface IntrinsicElements {
|
||||
"app-home": AppHome;
|
||||
"app-profile": AppProfile;
|
||||
"app-root": AppRoot;
|
||||
}
|
||||
}
|
||||
export { LocalJSX as JSX };
|
||||
declare module "@stencil/core" {
|
||||
export namespace JSX {
|
||||
interface IntrinsicElements {
|
||||
"app-home": LocalJSX.AppHome & JSXBase.HTMLAttributes<HTMLAppHomeElement>;
|
||||
"app-profile": LocalJSX.AppProfile & JSXBase.HTMLAttributes<HTMLAppProfileElement>;
|
||||
"app-root": LocalJSX.AppRoot & JSXBase.HTMLAttributes<HTMLAppRootElement>;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
.app-home {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
button {
|
||||
background: #5851ff;
|
||||
color: white;
|
||||
margin: 8px;
|
||||
border: none;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
padding: 16px 20px;
|
||||
border-radius: 2px;
|
||||
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1), 0 3px 6px rgba(0, 0, 0, 0.08);
|
||||
outline: 0;
|
||||
letter-spacing: 0.04em;
|
||||
transition: all 0.15s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
transform: translateY(1px);
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
import { newE2EPage } from '@stencil/core/testing';
|
||||
|
||||
describe('app-home', () => {
|
||||
it('renders', async () => {
|
||||
const page = await newE2EPage();
|
||||
await page.setContent('<app-home></app-home>');
|
||||
|
||||
const element = await page.find('app-home');
|
||||
expect(element).toHaveClass('hydrated');
|
||||
});
|
||||
|
||||
it('contains a "Profile Page" button', async () => {
|
||||
const page = await newE2EPage();
|
||||
await page.setContent('<app-home></app-home>');
|
||||
|
||||
const element = await page.find('app-home >>> button');
|
||||
expect(element.textContent).toEqual('Profile page');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,23 @@
|
|||
import { Component, h } from '@stencil/core';
|
||||
|
||||
@Component({
|
||||
tag: 'app-home',
|
||||
styleUrl: 'app-home.css',
|
||||
shadow: true,
|
||||
})
|
||||
export class AppHome {
|
||||
render() {
|
||||
return (
|
||||
<div class="app-home">
|
||||
<p>
|
||||
Welcome to the Stencil App Starter. You can use this starter to build entire apps all with web components using Stencil! Check out our docs on{' '}
|
||||
<a href="https://stenciljs.com">stenciljs.com</a> to get started.
|
||||
</p>
|
||||
|
||||
<stencil-route-link url="/profile/stencil">
|
||||
<button>Profile page</button>
|
||||
</stencil-route-link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
.app-profile {
|
||||
padding: 10px;
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
import { newE2EPage } from '@stencil/core/testing';
|
||||
|
||||
describe('app-profile', () => {
|
||||
it('renders', async () => {
|
||||
const page = await newE2EPage();
|
||||
await page.setContent('<app-profile></app-profile>');
|
||||
|
||||
const element = await page.find('app-profile');
|
||||
expect(element).toHaveClass('hydrated');
|
||||
});
|
||||
|
||||
it('displays the specified name', async () => {
|
||||
const page = await newE2EPage({ url: '/profile/joseph' });
|
||||
|
||||
const profileElement = await page.find('app-root >>> app-profile');
|
||||
const element = profileElement.shadowRoot.querySelector('div');
|
||||
expect(element.textContent).toContain('Hello! My name is Joseph.');
|
||||
});
|
||||
|
||||
// it('includes a div with the class "app-profile"', async () => {
|
||||
// const page = await newE2EPage({ url: '/profile/joseph' });
|
||||
|
||||
// I would like to use a selector like this above, but it does not seem to work
|
||||
// const element = await page.find('app-root >>> app-profile >>> div');
|
||||
// expect(element).toHaveClass('app-profile');
|
||||
// });
|
||||
});
|
|
@ -0,0 +1,46 @@
|
|||
import { AppProfile } from './app-profile';
|
||||
import { newSpecPage } from '@stencil/core/testing';
|
||||
|
||||
describe('app-profile', () => {
|
||||
describe('normalization', () => {
|
||||
it('returns a blank string if the name is undefined', async () => {
|
||||
const { rootInstance } = await newSpecPage({
|
||||
components: [AppProfile],
|
||||
html: '<app-profile></app-profile>',
|
||||
});
|
||||
expect(rootInstance.normalize(undefined)).toEqual('');
|
||||
});
|
||||
|
||||
it('returns a blank string if the name is null', async () => {
|
||||
const { rootInstance } = await newSpecPage({
|
||||
components: [AppProfile],
|
||||
html: '<app-profile></app-profile>',
|
||||
});
|
||||
expect(rootInstance.normalize(null)).toEqual('');
|
||||
});
|
||||
|
||||
it('capitalizes the first letter', async () => {
|
||||
const { rootInstance } = await newSpecPage({
|
||||
components: [AppProfile],
|
||||
html: '<app-profile></app-profile>',
|
||||
});
|
||||
expect(rootInstance.normalize('quincy')).toEqual('Quincy');
|
||||
});
|
||||
|
||||
it('lower-cases the following letters', async () => {
|
||||
const { rootInstance } = await newSpecPage({
|
||||
components: [AppProfile],
|
||||
html: '<app-profile></app-profile>',
|
||||
});
|
||||
expect(rootInstance.normalize('JOSEPH')).toEqual('Joseph');
|
||||
});
|
||||
|
||||
it('handles single letter names', async () => {
|
||||
const { rootInstance } = await newSpecPage({
|
||||
components: [AppProfile],
|
||||
html: '<app-profile></app-profile>',
|
||||
});
|
||||
expect(rootInstance.normalize('q')).toEqual('Q');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,28 @@
|
|||
import { Component, Prop, h } from '@stencil/core';
|
||||
import { MatchResults } from '@stencil-community/router';
|
||||
|
||||
@Component({
|
||||
tag: 'app-profile',
|
||||
styleUrl: 'app-profile.css',
|
||||
shadow: true,
|
||||
})
|
||||
export class AppProfile {
|
||||
@Prop() match: MatchResults;
|
||||
|
||||
normalize(name: string): string {
|
||||
if (name) {
|
||||
return name.substr(0, 1).toUpperCase() + name.substr(1).toLowerCase();
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.match && this.match.params.name) {
|
||||
return (
|
||||
<div class="app-profile">
|
||||
<p>Hello! My name is {this.normalize(this.match.params.name)}. My name was passed in through a route param!</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
header {
|
||||
background: #5851ff;
|
||||
color: white;
|
||||
height: 56px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.26);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.4rem;
|
||||
font-weight: 500;
|
||||
color: #fff;
|
||||
padding: 0 12px;
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
import { newE2EPage } from '@stencil/core/testing';
|
||||
|
||||
describe('app-root', () => {
|
||||
it('renders', async () => {
|
||||
const page = await newE2EPage({ url: '/' });
|
||||
|
||||
const element = await page.find('app-root');
|
||||
expect(element).toHaveClass('hydrated');
|
||||
});
|
||||
|
||||
it('renders the title', async () => {
|
||||
const page = await newE2EPage({ url: '/' });
|
||||
|
||||
const element = await page.find('app-root >>> h1');
|
||||
expect(element.textContent).toEqual('Stencil App Starter');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,27 @@
|
|||
import { Component, h } from '@stencil/core';
|
||||
|
||||
@Component({
|
||||
tag: 'app-root',
|
||||
styleUrl: 'app-root.css',
|
||||
shadow: true,
|
||||
})
|
||||
export class AppRoot {
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<header>
|
||||
<h1>Stencil App Starter</h1>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<stencil-router>
|
||||
<stencil-route-switch scrollTopOffset={0}>
|
||||
<stencil-route url="/" component="app-home" exact={true} />
|
||||
<stencil-route url="/profile/:name" component="app-profile" />
|
||||
</stencil-route-switch>
|
||||
</stencil-router>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
Global App CSS
|
||||
----------------------
|
||||
Use this file for styles that should be applied to all components.
|
||||
For example, "font-family" within the "body" selector is a CSS property
|
||||
most apps will want applied to all components.
|
||||
|
||||
Any global CSS variables should also be applied here.
|
||||
*/
|
||||
|
||||
body {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
export default async () => {
|
||||
/**
|
||||
* The code to be executed should be placed within a default function that is
|
||||
* exported by the global script. Ensure all of the code in the global script
|
||||
* is wrapped in the function() that is exported.
|
||||
*/
|
||||
};
|
|
@ -0,0 +1,25 @@
|
|||
<!DOCTYPE html>
|
||||
<html dir="ltr" lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Stencil Starter App</title>
|
||||
<meta name="Description" content="Welcome to the Stencil App Starter. You can use this starter to build entire apps all with web components using Stencil!">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=5.0">
|
||||
<meta name="theme-color" content="#16161d">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta http-equiv="x-ua-compatible" content="IE=Edge">
|
||||
|
||||
<script type="module" src="/build/app.esm.js"></script>
|
||||
<script nomodule src="/build/app.js"></script>
|
||||
<link href="/build/app.css" rel="stylesheet">
|
||||
|
||||
<link rel="apple-touch-icon" href="/assets/icon/icon.png">
|
||||
<link rel="icon" type="image/x-icon" href="/assets/icon/favicon.ico">
|
||||
<link rel="manifest" href="/manifest.json">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<app-root></app-root>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,2 @@
|
|||
export { Components, JSX } from './components';
|
||||
import '@stencil-community/router';
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"name": "Stencil Starter",
|
||||
"short_name": "Stencil",
|
||||
"start_url": "/",
|
||||
"display": "standalone",
|
||||
"icons": [{
|
||||
"src": "assets/icon/icon.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}],
|
||||
"background_color": "#16161d",
|
||||
"theme_color": "#16161d"
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
import { Config } from '@stencil/core';
|
||||
|
||||
// https://stenciljs.com/docs/config
|
||||
|
||||
export const config: Config = {
|
||||
globalStyle: 'src/global/app.css',
|
||||
globalScript: 'src/global/app.ts',
|
||||
taskQueue: 'async',
|
||||
outputTargets: [
|
||||
{
|
||||
type: 'www',
|
||||
// comment the following line to disable service workers in production
|
||||
serviceWorker: null,
|
||||
baseUrl: 'https://myapp.local/',
|
||||
},
|
||||
],
|
||||
};
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"allowUnreachableCode": false,
|
||||
"declaration": false,
|
||||
"experimentalDecorators": true,
|
||||
"lib": [
|
||||
"dom",
|
||||
"es2015"
|
||||
],
|
||||
"moduleResolution": "node",
|
||||
"module": "esnext",
|
||||
"target": "es2017",
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"jsx": "react",
|
||||
"jsxFactory": "h"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
Loading…
Reference in New Issue