├── demo ├── README.md ├── src │ ├── vite-env.d.ts │ ├── pages │ │ ├── style.less │ │ ├── sub.tsx │ │ ├── child-second.tsx │ │ ├── home.tsx │ │ └── child.tsx │ ├── index.less │ └── index.tsx ├── public │ ├── quark.png │ └── vite.svg ├── .vscode │ └── extensions.json ├── .gitignore ├── package.json ├── vite.config.js ├── index.html └── tsconfig.json ├── demo4gluang ├── src │ ├── index.less │ ├── vite-env.d.ts │ ├── app-header │ │ ├── suntzu.jpeg │ │ ├── index.less │ │ └── index.tsx │ ├── app-article │ │ ├── index.less │ │ └── index.tsx │ ├── store.ts │ ├── index.tsx │ └── gluang.ts ├── README.md ├── public │ ├── quark.png │ └── vite.svg ├── .vscode │ └── extensions.json ├── package.json ├── .gitignore ├── vite.config.js ├── index.html └── tsconfig.json ├── examples ├── using-cdn │ ├── .gitignore │ ├── vite.config.ts │ ├── README.md │ ├── README.en-US.md │ ├── package.json │ ├── tsconfig.json │ └── index.html ├── using-local-bundle │ ├── .gitignore │ ├── vite.config.ts │ ├── src │ │ ├── lib │ │ │ ├── index.d.ts │ │ │ ├── index.umd.js │ │ │ └── index.js │ │ └── main.ts │ ├── package.json │ ├── README.md │ ├── index.html │ ├── tsconfig.json │ └── README.en-US.md ├── using-local-package │ ├── .gitignore │ ├── my-component-0.0.0.tgz │ ├── vite.config.ts │ ├── src │ │ └── main.ts │ ├── README.md │ ├── package.json │ ├── index.html │ ├── README.en-US.md │ └── tsconfig.json ├── README.md └── README.en-US.md ├── packages ├── core │ ├── src │ │ ├── typings.d.ts │ │ ├── core │ │ │ ├── constants.js │ │ │ ├── options.js │ │ │ ├── util.ts │ │ │ ├── diff │ │ │ │ ├── catch-error.js │ │ │ │ └── props.js │ │ │ ├── render.js │ │ │ ├── component.js │ │ │ └── create-element.js │ │ ├── eventController.ts │ │ ├── dblKeyMap.ts │ │ ├── models.ts │ │ └── reactiveController.ts │ ├── logo.png │ ├── babel.config.js │ ├── test │ │ ├── components │ │ │ ├── hello-world.tsx │ │ │ ├── test-fragment.tsx │ │ │ ├── quark-counter.tsx │ │ │ ├── test-watch.tsx │ │ │ └── test-property.tsx │ │ └── render.test.ts │ ├── web-test-runner.config.js │ ├── tsconfig.json │ ├── LICENSE │ ├── package.json │ ├── rollup.config.ts │ ├── README.md │ └── README.en-US.md ├── router │ ├── .gitignore │ ├── src │ │ ├── global.d.ts │ │ ├── index.ts │ │ ├── style.css │ │ ├── eventEmitter.ts │ │ ├── utils.ts │ │ ├── quark-link.tsx │ │ └── router.ts │ ├── babel.config.js │ ├── vite.config.build-umd.ts │ ├── vite.config.build.ts │ ├── tsconfig.json │ ├── LICENSE │ └── package.json └── create-quarkc │ ├── index.js │ ├── template-quarkc-app │ ├── src │ │ ├── views │ │ │ ├── NotFound │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ ├── home │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ └── sub │ │ │ │ ├── index.css │ │ │ │ └── index.tsx │ │ ├── main.less │ │ ├── global.d.ts │ │ ├── main.ts │ │ └── components │ │ │ └── Header │ │ │ ├── index.css │ │ │ └── index.tsx │ ├── vite.config.js │ ├── babel.config.js │ ├── index.html │ ├── README.md │ ├── tsconfig.json │ ├── package.json │ └── LICENSE │ ├── template-quarkc-component │ ├── src │ │ ├── vite-env.d.ts │ │ ├── index.tsx │ │ └── index.less │ ├── vite.config.ts │ ├── .gitignore │ ├── tsconfig.json │ ├── index.html │ ├── vite.config.build.ts │ ├── index.vue.html │ ├── package.json │ └── README.md │ ├── template-quarkc-app-ts │ ├── src │ │ ├── views │ │ │ ├── NotFound │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ ├── home │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ └── sub │ │ │ │ ├── index.css │ │ │ │ └── index.tsx │ │ ├── main.less │ │ ├── global.d.ts │ │ ├── main.ts │ │ └── components │ │ │ └── Header │ │ │ ├── index.css │ │ │ └── index.tsx │ ├── vite.config.js │ ├── babel.config.js │ ├── index.html │ ├── README.md │ ├── tsconfig.json │ ├── package.json │ └── LICENSE │ ├── template-quarkc-component-ts │ ├── src │ │ ├── vite-env.d.ts │ │ ├── index.tsx │ │ └── index.less │ ├── vite.config.ts │ ├── .gitignore │ ├── tsconfig.json │ ├── index.html │ ├── vite.config.build.ts │ ├── index.vue.html │ ├── package.json │ └── README.md │ ├── build.config.ts │ ├── tsconfig.json │ ├── APP.dev.md │ ├── package.json │ ├── README.md │ ├── README.en-US.md │ └── COMPONENT.dev.md ├── .eslintignore ├── logo.png ├── pnpm-workspace.yaml ├── .npmrc ├── .gitignore ├── .vscode └── settings.json ├── .github └── workflows │ ├── sync-gitee.yml │ ├── release-for-create-quarkc.yml │ └── release-package.yml ├── LICENSE ├── package.json ├── .eslintrc.cjs ├── README.md └── README.en-US.md /demo/README.md: -------------------------------------------------------------------------------- 1 | # Quark + Vite -------------------------------------------------------------------------------- /demo4gluang/src/index.less: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/using-cdn/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /demo4gluang/README.md: -------------------------------------------------------------------------------- 1 | # Quark + Vite -------------------------------------------------------------------------------- /examples/using-local-bundle/.gitignore: -------------------------------------------------------------------------------- 1 | !lib -------------------------------------------------------------------------------- /examples/using-local-package/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/core/src/typings.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.json"; -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /dist 3 | *.test.js 4 | /demo -------------------------------------------------------------------------------- /demo/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /demo4gluang/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hellof2e/quark-core/HEAD/logo.png -------------------------------------------------------------------------------- /packages/router/.gitignore: -------------------------------------------------------------------------------- 1 | /development/ 2 | /test/ 3 | /node_modules/ 4 | /lib/ 5 | -------------------------------------------------------------------------------- /packages/create-quarkc/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import './dist/index.mjs' 4 | -------------------------------------------------------------------------------- /demo/public/quark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hellof2e/quark-core/HEAD/demo/public/quark.png -------------------------------------------------------------------------------- /packages/router/src/global.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.css'; 2 | declare module '*.css?inline'; 3 | -------------------------------------------------------------------------------- /packages/core/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hellof2e/quark-core/HEAD/packages/core/logo.png -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | # all packages in direct subdirs of packages/ 3 | - 'packages/*' -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry="https://registry.npmjs.org/" 2 | strict-peer-dependencies=false 3 | auto-install-peers=false -------------------------------------------------------------------------------- /packages/create-quarkc/template-quarkc-app/src/views/NotFound/index.less: -------------------------------------------------------------------------------- 1 | a { 2 | color: #53c4ff; 3 | } 4 | -------------------------------------------------------------------------------- /packages/create-quarkc/template-quarkc-component/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /demo4gluang/public/quark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hellof2e/quark-core/HEAD/demo4gluang/public/quark.png -------------------------------------------------------------------------------- /packages/create-quarkc/template-quarkc-app-ts/src/views/NotFound/index.less: -------------------------------------------------------------------------------- 1 | a { 2 | color: #53c4ff; 3 | } 4 | -------------------------------------------------------------------------------- /packages/create-quarkc/template-quarkc-component-ts/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /demo/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"] 3 | } 4 | -------------------------------------------------------------------------------- /demo4gluang/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"] 3 | } 4 | -------------------------------------------------------------------------------- /demo4gluang/src/app-header/suntzu.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hellof2e/quark-core/HEAD/demo4gluang/src/app-header/suntzu.jpeg -------------------------------------------------------------------------------- /packages/create-quarkc/template-quarkc-app-ts/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | 3 | 4 | export default defineConfig({}) -------------------------------------------------------------------------------- /packages/create-quarkc/template-quarkc-app/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | 3 | 4 | export default defineConfig({}) -------------------------------------------------------------------------------- /demo4gluang/src/app-article/index.less: -------------------------------------------------------------------------------- 1 | main, 2 | aside { 3 | max-width: 50em; 4 | margin: auto; 5 | } 6 | aside { 7 | position: relative; 8 | } 9 | -------------------------------------------------------------------------------- /examples/using-cdn/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | 3 | // https://vitejs.dev/config/ 4 | export default defineConfig({}); 5 | -------------------------------------------------------------------------------- /examples/using-local-package/my-component-0.0.0.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hellof2e/quark-core/HEAD/examples/using-local-package/my-component-0.0.0.tgz -------------------------------------------------------------------------------- /examples/using-local-bundle/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | 3 | // https://vitejs.dev/config/ 4 | export default defineConfig({}); 5 | -------------------------------------------------------------------------------- /examples/using-local-package/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | 3 | // https://vitejs.dev/config/ 4 | export default defineConfig({}); 5 | -------------------------------------------------------------------------------- /packages/create-quarkc/template-quarkc-app/babel.config.js: -------------------------------------------------------------------------------- 1 | const plugins = [ 2 | ['@babel/plugin-transform-runtime'], 3 | ]; 4 | 5 | module.exports = { plugins } -------------------------------------------------------------------------------- /demo4gluang/src/app-header/index.less: -------------------------------------------------------------------------------- 1 | header { 2 | max-width: 50em; 3 | margin: auto; 4 | } 5 | 6 | .btn { 7 | color: aquamarine; 8 | cursor: pointer; 9 | } -------------------------------------------------------------------------------- /packages/create-quarkc/template-quarkc-app-ts/babel.config.js: -------------------------------------------------------------------------------- 1 | const plugins = [ 2 | ['@babel/plugin-transform-runtime'], 3 | ]; 4 | 5 | module.exports = { plugins } -------------------------------------------------------------------------------- /packages/create-quarkc/template-quarkc-component-ts/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | 3 | // https://vitejs.dev/config/ 4 | export default defineConfig({}); 5 | -------------------------------------------------------------------------------- /packages/create-quarkc/template-quarkc-component/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | 3 | // https://vitejs.dev/config/ 4 | export default defineConfig({}); 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /cache 4 | /.nyc_output 5 | /coverage 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | 11 | lib 12 | es 13 | umd 14 | tsc 15 | dist 16 | types -------------------------------------------------------------------------------- /packages/core/src/core/constants.js: -------------------------------------------------------------------------------- 1 | export const EMPTY_OBJ = {}; 2 | export const EMPTY_ARR = []; 3 | export const IS_NON_DIMENSIONAL = /acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i; 4 | -------------------------------------------------------------------------------- /demo4gluang/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/core/babel.config.js: -------------------------------------------------------------------------------- 1 | const presets = [["@babel/preset-env"]]; 2 | const plugins = [ 3 | ["@babel/plugin-transform-runtime"], 4 | ["@babel/plugin-proposal-decorators", { "version": "legacy" }], 5 | ]; 6 | 7 | export default { presets, plugins }; 8 | -------------------------------------------------------------------------------- /packages/router/babel.config.js: -------------------------------------------------------------------------------- 1 | const presets = [["@babel/preset-env"]]; 2 | const plugins = [ 3 | ["@babel/plugin-transform-runtime"], 4 | ["@babel/plugin-proposal-decorators", { "version": "legacy" }], 5 | ]; 6 | 7 | export default { presets, plugins }; 8 | -------------------------------------------------------------------------------- /packages/router/src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2021 Google LLC 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | export * from './routes.js'; 7 | export { Router } from './router.js'; 8 | export { default as QuarkLink } from './quark-link'; -------------------------------------------------------------------------------- /demo/src/pages/style.less: -------------------------------------------------------------------------------- 1 | :host .main { 2 | margin: 0 auto; 3 | padding: 2rem; 4 | text-align: center; 5 | border: 2px solid #0da6e9; 6 | width: 500px; 7 | display: block; 8 | } 9 | 10 | :host li { 11 | list-style: none; 12 | } 13 | -------------------------------------------------------------------------------- /examples/using-cdn/README.md: -------------------------------------------------------------------------------- 1 | 简体中文 | [English](./README.en-US.md) 2 | 3 | # 使用 CDN 4 | 5 | ## 运行 6 | 7 | ```bash 8 | npm i 9 | npm start 10 | ``` 11 | 12 | ## 提示 13 | 14 | 默认需要引入 `UMD` 格式的打包产物。由于组件的 `quarkc` 依赖默认被外置(External),你应当在引入组件 CDN 之前先引入 `quarkc` 15 | -------------------------------------------------------------------------------- /packages/create-quarkc/template-quarkc-app-ts/src/views/home/index.less: -------------------------------------------------------------------------------- 1 | h3 { 2 | margin: 40px 0 0; 3 | } 4 | ul { 5 | list-style-type: none; 6 | padding: 0; 7 | } 8 | li { 9 | display: inline-block; 10 | margin: 0 10px; 11 | } 12 | a { 13 | color: #53c4ff; 14 | } -------------------------------------------------------------------------------- /packages/create-quarkc/template-quarkc-app/src/views/home/index.less: -------------------------------------------------------------------------------- 1 | h3 { 2 | margin: 40px 0 0; 3 | } 4 | ul { 5 | list-style-type: none; 6 | padding: 0; 7 | } 8 | li { 9 | display: inline-block; 10 | margin: 0 10px; 11 | } 12 | a { 13 | color: #53c4ff; 14 | } -------------------------------------------------------------------------------- /packages/create-quarkc/template-quarkc-app-ts/src/views/sub/index.css: -------------------------------------------------------------------------------- 1 | h3 { 2 | margin: 40px 0 0; 3 | } 4 | ul { 5 | list-style-type: none; 6 | padding: 0; 7 | } 8 | li { 9 | display: inline-block; 10 | margin: 0 10px; 11 | } 12 | a { 13 | color: #53c4ff; 14 | } 15 | -------------------------------------------------------------------------------- /packages/create-quarkc/template-quarkc-app/src/main.less: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: Avenir, Helvetica, Arial, sans-serif; 3 | -webkit-font-smoothing: antialiased; 4 | -moz-osx-font-smoothing: grayscale; 5 | text-align: center; 6 | color: #2c3e50; 7 | margin-top: 60px; 8 | } 9 | -------------------------------------------------------------------------------- /packages/create-quarkc/template-quarkc-app/src/views/sub/index.css: -------------------------------------------------------------------------------- 1 | h3 { 2 | margin: 40px 0 0; 3 | } 4 | ul { 5 | list-style-type: none; 6 | padding: 0; 7 | } 8 | li { 9 | display: inline-block; 10 | margin: 0 10px; 11 | } 12 | a { 13 | color: #53c4ff; 14 | } 15 | -------------------------------------------------------------------------------- /demo/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | pnpm-debug.log* 5 | 6 | node_modules 7 | dist 8 | dist-ssr 9 | *.local 10 | 11 | # Editor directories and files 12 | !.vscode/extensions.json 13 | .idea 14 | .DS_Store 15 | *.suo 16 | *.ntvs* 17 | *.njsproj 18 | *.sln 19 | *.sw? 20 | -------------------------------------------------------------------------------- /packages/create-quarkc/template-quarkc-app-ts/src/main.less: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: Avenir, Helvetica, Arial, sans-serif; 3 | -webkit-font-smoothing: antialiased; 4 | -moz-osx-font-smoothing: grayscale; 5 | text-align: center; 6 | color: #2c3e50; 7 | margin-top: 60px; 8 | } 9 | -------------------------------------------------------------------------------- /demo4gluang/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | pnpm-debug.log* 5 | 6 | node_modules 7 | dist 8 | dist-ssr 9 | *.local 10 | 11 | # Editor directories and files 12 | !.vscode/extensions.json 13 | .idea 14 | .DS_Store 15 | *.suo 16 | *.ntvs* 17 | *.njsproj 18 | *.sln 19 | *.sw? 20 | -------------------------------------------------------------------------------- /demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview" 10 | }, 11 | "devDependencies": { 12 | "vite-plugin-dev-inspector": "^2.2.4" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/create-quarkc/template-quarkc-app-ts/src/global.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.css'; 2 | declare module '*.css?inline'; 3 | declare module '*.less'; 4 | declare module '*.less?inline'; 5 | declare module "*.png"; 6 | declare module "*.svg"; 7 | declare module "*.jpeg"; 8 | declare module "*.jpg"; 9 | declare module "*.md"; 10 | declare module "*.tsx"; -------------------------------------------------------------------------------- /packages/create-quarkc/template-quarkc-app/src/global.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.css'; 2 | declare module '*.css?inline'; 3 | declare module '*.less'; 4 | declare module '*.less?inline'; 5 | declare module "*.png"; 6 | declare module "*.svg"; 7 | declare module "*.jpeg"; 8 | declare module "*.jpg"; 9 | declare module "*.md"; 10 | declare module "*.tsx"; -------------------------------------------------------------------------------- /examples/using-cdn/README.en-US.md: -------------------------------------------------------------------------------- 1 | [简体中文](./README.md) | English 2 | 3 | # use cdn 4 | 5 | ## run 6 | 7 | ```bash 8 | npm i 9 | npm start 10 | ``` 11 | 12 | ## tip 13 | By default you should introduce the umd bundle. Because the component's quarkc dependency is by default externalized, you should introduce quarkc first before introducing component's CDN. -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | ## 组件产物的使用示例 2 | 3 | Some examples of how to use the Quarkc components bundle. These are great for reproductions of issues. 4 | 5 | [Quarkc 工程](https://github.com/hellof2e/quark-core#%E7%BB%84%E4%BB%B6%E8%B5%B7%E6%89%8B%E6%9E%B6%E6%A8%A1%E7%89%88)构建的组件,打包产物在 `lib` 文件夹下。 6 | 7 | 组件产物使用方式可以是: 8 | 9 | - 直接使用本地 bundle 10 | - CDN 11 | - NPM 12 | -------------------------------------------------------------------------------- /examples/using-local-package/src/main.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * everytime local my-component files change, you should re-run npm pack 3 | * to generate the latest .tgz package 4 | * 每次当本地的my-component组件文件发生变化时,你应当重新运行npm pack以生成最新的.tgz包 5 | */ 6 | // if use import syntax(如果使用的是import语法) 7 | import 'my-component' 8 | 9 | // or if use require(如果使用的是require)——AMD/Commonjs 10 | // require('my-component') 11 | -------------------------------------------------------------------------------- /packages/create-quarkc/template-quarkc-app-ts/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Hello Quark 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /packages/create-quarkc/template-quarkc-app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Hello Quark 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /demo4gluang/src/store.ts: -------------------------------------------------------------------------------- 1 | import { createGluang } from './gluang'; 2 | 3 | class MyState extends createGluang { 4 | [x: string]: any; 5 | 6 | // global state 7 | static get stateVars() { 8 | return { 9 | author: 'Guess who?', 10 | }; 11 | } 12 | 13 | // global function 14 | // handleChange() { 15 | // this.author = 'Sun Tzu'; 16 | // } 17 | } 18 | 19 | export const store = new MyState(); -------------------------------------------------------------------------------- /examples/using-cdn/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "use-cdn", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "start": "vite --open" 6 | }, 7 | "type": "module", 8 | "keywords": [ 9 | "frontend", 10 | "quark", 11 | "web components" 12 | ], 13 | "devDependencies": { 14 | "@types/node": "^20.2.5", 15 | "vite": "^4.3.0" 16 | }, 17 | "engines": { 18 | "node": ">=14.18.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /examples/using-local-bundle/src/lib/index.d.ts: -------------------------------------------------------------------------------- 1 | import { QuarkElement } from "quarkc"; 2 | declare global { 3 | interface HTMLElementTagNameMap { 4 | "my-component": MyComponent; 5 | } 6 | } 7 | declare class MyComponent extends QuarkElement { 8 | count: number; 9 | text: string; 10 | add: () => void; 11 | componentDidMount(): void; 12 | render(): any; 13 | } 14 | export default MyComponent; 15 | -------------------------------------------------------------------------------- /packages/create-quarkc/template-quarkc-component/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | lib 15 | 16 | # Editor directories and files 17 | .vscode/* 18 | !.vscode/extensions.json 19 | .idea 20 | .DS_Store 21 | *.suo 22 | *.ntvs* 23 | *.njsproj 24 | *.sln 25 | *.sw? 26 | -------------------------------------------------------------------------------- /demo4gluang/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import { resolve }from 'path' 3 | 4 | // https://vitejs.dev/config/ 5 | const rootPtah = resolve(__dirname, "../"); 6 | export default defineConfig({ 7 | rootPtah, 8 | base: "./", 9 | resolve: { 10 | alias: [ 11 | { 12 | find: "quarkc", 13 | replacement: resolve(__dirname, "../packages/core/src/index.ts"), 14 | }, 15 | ], 16 | }, 17 | }) 18 | -------------------------------------------------------------------------------- /packages/create-quarkc/template-quarkc-component-ts/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | lib 15 | 16 | # Editor directories and files 17 | .vscode/* 18 | !.vscode/extensions.json 19 | .idea 20 | .DS_Store 21 | *.suo 22 | *.ntvs* 23 | *.njsproj 24 | *.sln 25 | *.sw? 26 | -------------------------------------------------------------------------------- /demo4gluang/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { QuarkElement, customElement } from "quarkc" 2 | import style from "./index.less?inline" 3 | 4 | import './app-article' 5 | import './app-header' 6 | 7 | @customElement({ tag: "my-app", style }) 8 | class MyApp extends QuarkElement { 9 | render() { 10 | return ( 11 |
12 | 13 | 14 |
15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/using-local-bundle/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "use-local-bundle", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "start": "vite --open" 6 | }, 7 | "type": "module", 8 | "keywords": [ 9 | "frontend", 10 | "quark", 11 | "web components" 12 | ], 13 | "devDependencies": { 14 | "@types/node": "^20.2.5", 15 | "vite": "^4.3.0" 16 | }, 17 | "engines": { 18 | "node": ">=14.18.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/create-quarkc/build.config.ts: -------------------------------------------------------------------------------- 1 | import { defineBuildConfig } from 'unbuild' 2 | 3 | export default defineBuildConfig({ 4 | entries: ['src/index'], 5 | clean: true, 6 | rollup: { 7 | inlineDependencies: true, 8 | esbuild: { 9 | minify: true, 10 | }, 11 | }, 12 | alias: { 13 | // we can always use non-transpiled code since we support 14.18.0+ 14 | prompts: 'prompts/lib/index.js', 15 | }, 16 | }) 17 | -------------------------------------------------------------------------------- /packages/create-quarkc/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["build.config.ts", "src", "__tests__"], 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "target": "ES2020", 6 | "module": "ES2020", 7 | "moduleResolution": "bundler", 8 | "strict": true, 9 | "skipLibCheck": true, 10 | "declaration": false, 11 | "sourceMap": false, 12 | "noUnusedLocals": true, 13 | "esModuleInterop": true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/using-local-package/README.md: -------------------------------------------------------------------------------- 1 | 简体中文 | [English](./README.en-US.md) 2 | 3 | # 使用本地Quarkc包示例 4 | 5 | ## 运行 6 | 7 | ```bash 8 | npm i 9 | # 像这样指定在本地文件系统上的tgz包路径来安装本地依赖,或者在npm i之前在package.json中手动列出本地依赖和路径 10 | # 在这种使用方式下,quarkc作为my-component的依赖会被自动安装 11 | npm i ./my-component-0.0.0.tgz --save 12 | npm start 13 | ``` 14 | 15 | ## 额外说明 16 | `my-component-0.0.0.tgz`文件是直接从component示例中拷贝过来的(通过在component示例中执行`npm run build && npm pack`命令生成)。 17 | -------------------------------------------------------------------------------- /examples/using-local-package/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "use-local-package", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "start": "vite --open" 6 | }, 7 | "type": "module", 8 | "keywords": [ 9 | "frontend", 10 | "quark", 11 | "web components" 12 | ], 13 | "devDependencies": { 14 | "@types/node": "^20.2.5", 15 | "vite": "^4.3.0" 16 | }, 17 | "engines": { 18 | "node": ">=14.18.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /examples/README.en-US.md: -------------------------------------------------------------------------------- 1 | ## Example of using component bundle 2 | 3 | Some examples of how to use the Quarkc components bundle. These are great for reproductions of issues. 4 | 5 | [Quarkc project](https://github.com/hellof2e/quark-core#%E7%BB%84%E4%BB%B6%E8%B5%B7%E6%89%8B%E6%9E%B6% E6%A8%A1%E7%89%88), the packaged product is in the `lib` folder. 6 | 7 | Component products can be used in the following ways: 8 | 9 | - Use local bundle directly 10 | - CDN 11 | - NPM -------------------------------------------------------------------------------- /examples/using-local-bundle/README.md: -------------------------------------------------------------------------------- 1 | 简体中文 | [English](./README.en-US.md) 2 | 3 | # 使用本地Quarkc组件打包产物示例 4 | 5 | ## 运行 6 | 7 | ```bash 8 | npm i 9 | # 重要:需要确保使用Quarkc组件之前已经安装quarkc依赖 10 | # 使用quark-cli component模板生成的项目,默认情况下quarkc依赖会被外置从而不被打包进产物中 11 | # 这么做是为了避免分发的单个组件中包含不同版本的quarkc代码,从而影响打包工具的tree-shaking 12 | # 以减小应用最终的打包体积 13 | npm i quarkc --save 14 | npm start 15 | ``` 16 | 17 | ## 额外说明 18 | `src/lib`目录是直接从component示例中拷贝过来的(在component示例中执行`npm run build`命令生成的`lib`目录)。 19 | -------------------------------------------------------------------------------- /packages/create-quarkc/template-quarkc-app/README.md: -------------------------------------------------------------------------------- 1 | # Quark App 2 | 3 | 无框架前端应用模版,底层基于 Web components。 4 | 5 | > 本工程用于脱离 React、Vue 等技术栈来构建一个独立的 Web 应用,[技术文档](https://github.com/hellof2e/quark)。 6 | 7 | - [x] 支持路由 8 | - [x] 支持数据绑定 9 | - [x] 没有虚拟 DOM 10 | - [x] 支持 TSX / JSX 11 | - [x] 支持将 Markdown 渲染成 html 12 | - [x] 浏览器原生,没有依赖 13 | - [x] 开发环境支持热更新 14 | 15 | 16 | ## 如何使用 17 | 18 | ``` 19 | npm install 20 | npm run dev 21 | ``` 22 | 23 | ## 打包产物 24 | 25 | ``` 26 | npm run build 27 | ``` 28 | -------------------------------------------------------------------------------- /packages/core/test/components/hello-world.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | QuarkElement, 3 | customElement, 4 | } from "../../src/main" 5 | 6 | const tag = 'hello-world'; 7 | 8 | declare global { 9 | interface HTMLElementTagNameMap { 10 | [tag]: HelloWorld; 11 | } 12 | } 13 | 14 | @customElement({ tag, style: '.test { color: rgb(136, 170, 255); font-size: 24px; }' }) 15 | class HelloWorld extends QuarkElement { 16 | render() { 17 | return
hello, world!
; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/create-quarkc/template-quarkc-app-ts/src/views/NotFound/index.tsx: -------------------------------------------------------------------------------- 1 | import { QuarkElement, customElement, state, createRef, } from "quarkc" 2 | import style from "./index.less?inline" 3 | 4 | @customElement({ tag: "app-not-found", style }) 5 | class AppNotFound extends QuarkElement { 6 | handleGoBack = () => { 7 | history.go(-1) 8 | } 9 | 10 | render() { 11 | return ( 12 | <> 13 | 404 Page Not Found 14 | Click to go back 15 | 16 | ) 17 | } 18 | } -------------------------------------------------------------------------------- /packages/create-quarkc/template-quarkc-app/src/views/NotFound/index.tsx: -------------------------------------------------------------------------------- 1 | import { QuarkElement, customElement, state, createRef, } from "quarkc" 2 | import style from "./index.less?inline" 3 | 4 | @customElement({ tag: "app-not-found", style }) 5 | class AppNotFound extends QuarkElement { 6 | handleGoBack = () => { 7 | history.go(-1) 8 | } 9 | 10 | render() { 11 | return ( 12 | <> 13 | 404 Page Not Found 14 | Click to go back 15 | 16 | ) 17 | } 18 | } -------------------------------------------------------------------------------- /packages/core/test/components/test-fragment.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | QuarkElement, 3 | customElement, 4 | } from "../../src/main" 5 | 6 | const tag = 'test-fragment'; 7 | 8 | declare global { 9 | interface HTMLElementTagNameMap { 10 | [tag]: TestFragment; 11 | } 12 | } 13 | 14 | @customElement({ tag }) 15 | class TestFragment extends QuarkElement { 16 | render() { 17 | return ( 18 | <> 19 |
root1
20 |
root2
21 | 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.codeActionsOnSave": { 3 | "source.fixAll": "explicit" 4 | }, 5 | "files.associations": { 6 | "*.vue": "html" 7 | }, 8 | "eslint.options": { 9 | "configFile": "./.eslintrc.cjs" 10 | }, 11 | "eslint.validate": ["javascript", "vue", "html", "typescript"], 12 | "workbench.startupEditor": "none", 13 | "stylelint.validate": ["css", "less"], 14 | "less.validate": false, 15 | "css.validate": false, 16 | "cSpell.words": [ 17 | "quarkc" 18 | ] 19 | } -------------------------------------------------------------------------------- /packages/core/web-test-runner.config.js: -------------------------------------------------------------------------------- 1 | import { fileURLToPath } from 'url'; 2 | import { esbuildPlugin } from '@web/dev-server-esbuild' 3 | 4 | const tsConfigFileURL = fileURLToPath(new URL('./tsconfig.json', import.meta.url)); 5 | 6 | export default { 7 | files: ['src/**/*.test.ts', 'src/**/*.spec.ts'], 8 | plugins: [esbuildPlugin({ 9 | ts: true, 10 | tsconfig: tsConfigFileURL, 11 | tsx: true, 12 | json: true, 13 | define: { 14 | 'process.env.NODE_ENV': `'${process.env.NODE_ENV}'` 15 | }, 16 | })], 17 | }; 18 | -------------------------------------------------------------------------------- /examples/using-local-bundle/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Hello Quark 6 | 7 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /examples/using-local-package/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Hello Quark 6 | 7 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /packages/core/src/core/options.js: -------------------------------------------------------------------------------- 1 | import { _catchError } from './diff/catch-error'; 2 | 3 | /** 4 | * The `option` object can potentially contain callback functions 5 | * that are called during various stages of our renderer. This is the 6 | * foundation on which all our addons like `quark/debug`, `quark/compat`, 7 | * and `quark/hooks` are based on. See the `Options` type in `internal.d.ts` 8 | * for a full list of available option hooks (most editors/IDEs allow you to 9 | * ctrl+click or cmd+click on mac the type definition below). 10 | */ 11 | const options = { 12 | _catchError 13 | }; 14 | 15 | export default options; 16 | -------------------------------------------------------------------------------- /packages/create-quarkc/template-quarkc-app/src/views/sub/index.tsx: -------------------------------------------------------------------------------- 1 | import { QuarkElement, customElement, state, } from "quarkc"; 2 | import style from "./index.css?inline"; 3 | 4 | @customElement({ tag: "app-sub", style }) 5 | class Sub extends QuarkElement { 6 | @state() 7 | title = 'docs' 8 | 9 | render() { 10 | return ( 11 | <> 12 |

Menu

13 | 18 |

Welcome to sub page

19 | 20 | ); 21 | } 22 | } -------------------------------------------------------------------------------- /packages/create-quarkc/template-quarkc-app-ts/src/views/sub/index.tsx: -------------------------------------------------------------------------------- 1 | import { QuarkElement, customElement, state, } from "quarkc"; 2 | import style from "./index.css?inline"; 3 | 4 | @customElement({ tag: "app-sub", style }) 5 | class Sub extends QuarkElement { 6 | @state() 7 | title = 'docs' 8 | 9 | render() { 10 | return ( 11 | <> 12 |

Menu

13 | 18 |

Welcome to sub page

19 | 20 | ); 21 | } 22 | } -------------------------------------------------------------------------------- /packages/router/vite.config.build-umd.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | 3 | 4 | export default defineConfig({ 5 | build: { 6 | lib: { 7 | entry: "./src/index.ts", 8 | formats: ['umd'], 9 | fileName: (format, entryName) => { 10 | return `${entryName}.${format}.js`; 11 | }, 12 | name: 'quarkRouter' 13 | }, 14 | rollupOptions: { 15 | external: [ 16 | 'quarkc', 17 | ], 18 | output: { 19 | dir: "umd", 20 | globals: { 21 | quarkc: 'Quarkc', 22 | }, 23 | }, 24 | plugins: [ 25 | ], 26 | }, 27 | }, 28 | }); 29 | -------------------------------------------------------------------------------- /packages/create-quarkc/template-quarkc-app-ts/README.md: -------------------------------------------------------------------------------- 1 | # Quark App 2 | 3 | 基于本工程,您可以构建前端应用。 4 | 5 | 可以用下面命令生成本工程模版! 6 | 7 | ```bash 8 | npm create quarkc@latest 9 | ``` 10 | 11 | 文档见:https://quark-ecosystem.github.io/quarkc-docs/#/ 12 | 13 | 无框架前端应用模版,底层基于 Web components。 14 | 15 | > 本工程用于脱离 React、Vue 等技术栈来构建一个独立的 Web 应用,[技术文档](https://github.com/hellof2e/quark)。 16 | 17 | - [x] 支持路由 18 | - [x] 支持数据绑定 19 | - [x] 没有虚拟 DOM 20 | - [x] 支持 TSX / JSX 21 | - [x] 支持将 Markdown 渲染成 html 22 | - [x] 浏览器原生,没有依赖 23 | - [x] 开发环境支持热更新 24 | 25 | 26 | ## 如何使用 27 | 28 | ``` 29 | npm install 30 | npm run dev 31 | ``` 32 | 33 | ## 打包产物 34 | 35 | ``` 36 | npm run build 37 | ``` 38 | -------------------------------------------------------------------------------- /demo/src/pages/sub.tsx: -------------------------------------------------------------------------------- 1 | import { QuarkElement, property, customElement } from "quarkc" 2 | import style from "./style.less?inline" 3 | 4 | declare global { 5 | interface HTMLElementTagNameMap { 6 | "sub-component": MyComponent; 7 | } 8 | } 9 | 10 | @customElement({ tag: "sub-component", style }) 11 | class MyComponent extends QuarkElement { 12 | @property({ type: String }) 13 | id = "" 14 | 15 | 16 | componentDidMount() { 17 | // console.log("dom loaded!", 'sub') 18 | // ... 19 | } 20 | 21 | render() { 22 | return ( 23 |
24 | sub-page id={this.id} 25 |
26 | ); 27 | } 28 | } 29 | 30 | export default MyComponent; 31 | -------------------------------------------------------------------------------- /.github/workflows/sync-gitee.yml: -------------------------------------------------------------------------------- 1 | name: Sync to Gitee 2 | 3 | on: [ push, delete, create ] 4 | 5 | jobs: 6 | deploy-site-sync-gitee: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Sync to Gitee 10 | uses: wearerequired/git-mirror-action@master 11 | env: 12 | # 注意在 Settings -> Secrets 配置 GITEE_RSA_PRIVATE_KEY 13 | # 也可以在组织 Settings -> Secrets 配置全局性的 GITEE_RSA_PRIVATE_KEY 14 | SSH_PRIVATE_KEY: ${{ secrets.GITEE_RSA_PRIVATE_KEY }} 15 | with: 16 | # 注意替换为你的 GitHub 源仓库地址 17 | source-repo: git@github.com:hellof2e/quark.git 18 | # 注意替换为你的 Gitee 目标仓库地址 19 | destination-repo: git@gitee.com:hellof2e/quark.git -------------------------------------------------------------------------------- /demo/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import { resolve }from 'path' 3 | import inspector from 'vite-plugin-dev-inspector' 4 | 5 | // https://vitejs.dev/config/ 6 | const rootPtah = resolve(__dirname, "../"); 7 | export default defineConfig({ 8 | rootPtah, 9 | base: "./", 10 | plugins: [ 11 | inspector({ 12 | toggleButtonVisibility: 'never' 13 | }), 14 | ], 15 | resolve: { 16 | alias: [ 17 | { 18 | find: "quarkc", 19 | replacement: resolve(__dirname, "../packages/core/src/main.ts"), 20 | }, 21 | { 22 | find: "quark-router", 23 | replacement: resolve(__dirname, "../packages/router/src/index.ts"), 24 | }, 25 | ], 26 | }, 27 | }) 28 | -------------------------------------------------------------------------------- /packages/core/test/components/quark-counter.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | QuarkElement, 3 | customElement, 4 | state, 5 | } from "../../src/main" 6 | 7 | const tag = 'quark-counter'; 8 | 9 | declare global { 10 | interface HTMLElementTagNameMap { 11 | [tag]: QuarkCounter; 12 | } 13 | } 14 | 15 | @customElement({ tag }) 16 | class QuarkCounter extends QuarkElement { 17 | @state() 18 | count = 0; 19 | 20 | add = () => { 21 | this.count++; 22 | } 23 | 24 | render() { 25 | return ( 26 |
27 |
{this.count}
28 | 29 |
30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/router/src/style.css: -------------------------------------------------------------------------------- 1 | :host a:link, 2 | :host a:hover, 3 | :host a:visited, 4 | :host a:active 5 | { 6 | text-decoration: var(--quark-link-text-decoration, none); 7 | font-size: var(--quark-link-font-size, medium); 8 | font-family: var(--quark-link-font-family, inherit); 9 | } 10 | 11 | :host a:link { 12 | color: var(--quark-link-color, inherit); 13 | } 14 | 15 | :host a:hover { 16 | color: var(--quark-link-hover-color, var(--quark-link-color, inherit)); 17 | } 18 | 19 | :host a:visited { 20 | color: var(--quark-link-visited-color, var(--quark-link-color, inherit)); 21 | } 22 | 23 | :host a:active { 24 | color: var(--quark-link-active-color, var(--quark-link-color, inherit)); 25 | } -------------------------------------------------------------------------------- /examples/using-cdn/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "strict": false, 7 | "jsx": "react", 8 | "jsxFactory": "QuarkElement.h", 9 | "jsxFragmentFactory": "QuarkElement.Fragment", 10 | "sourceMap": false, 11 | "declaration": true, 12 | "declarationDir": "lib/types", 13 | "emitDeclarationOnly": true, 14 | "removeComments": true, 15 | "resolveJsonModule": true, 16 | "esModuleInterop": true, 17 | "skipLibCheck": true, 18 | "experimentalDecorators": true, 19 | "lib": ["esnext", "dom"] 20 | }, 21 | "include": ["src/**/*"], 22 | "exclude": ["node_modules/**"] 23 | } 24 | -------------------------------------------------------------------------------- /examples/using-local-package/README.en-US.md: -------------------------------------------------------------------------------- 1 | [简体中文](./README.md) | English 2 | 3 | # use local Quarkc component package example 4 | 5 | ## run 6 | 7 | ```bash 8 | npm i 9 | # install local dependency by specifying the .tgz package path on local file system like this 10 | # or list the local dependency and it's path in the package.json before npm i 11 | # in this use case, quarkc will be installed automatically as a dependency of my-component 12 | npm i ../component/my-component-0.0.0.tgz --save 13 | npm start 14 | ``` 15 | 16 | ## extra description 17 | The `my-component-0.0.0.tgz` file is directly copied from the component example (generated by executing command `npm run build && npm pack` under the component example). 18 | -------------------------------------------------------------------------------- /packages/create-quarkc/template-quarkc-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "rootDir": ".", 5 | "target": "esnext", 6 | "module": "esnext", 7 | "moduleResolution": "node", 8 | "strict": false, 9 | "jsx": "react", 10 | "jsxFactory": "QuarkElement.h", 11 | "jsxFragmentFactory": "QuarkElement.Fragment", 12 | "sourceMap": false, 13 | "declaration": false, 14 | "removeComments": true, 15 | "resolveJsonModule": true, 16 | "esModuleInterop": true, 17 | "skipLibCheck": true, 18 | "experimentalDecorators": true, 19 | "lib": ["esnext", "dom"], 20 | }, 21 | "include": ["src/**/*"], 22 | "exclude": ["node_modules/**"] 23 | } 24 | 25 | -------------------------------------------------------------------------------- /examples/using-local-bundle/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "strict": false, 7 | "jsx": "react", 8 | "jsxFactory": "QuarkElement.h", 9 | "jsxFragmentFactory": "QuarkElement.Fragment", 10 | "sourceMap": false, 11 | "declaration": true, 12 | "declarationDir": "lib/types", 13 | "emitDeclarationOnly": true, 14 | "removeComments": true, 15 | "resolveJsonModule": true, 16 | "esModuleInterop": true, 17 | "skipLibCheck": true, 18 | "experimentalDecorators": true, 19 | "lib": ["esnext", "dom"] 20 | }, 21 | "include": ["src/**/*"], 22 | "exclude": ["node_modules/**"] 23 | } 24 | -------------------------------------------------------------------------------- /packages/create-quarkc/template-quarkc-app-ts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "rootDir": ".", 5 | "target": "esnext", 6 | "module": "esnext", 7 | "moduleResolution": "node", 8 | "strict": false, 9 | "jsx": "react", 10 | "jsxFactory": "QuarkElement.h", 11 | "jsxFragmentFactory": "QuarkElement.Fragment", 12 | "sourceMap": false, 13 | "declaration": false, 14 | "removeComments": true, 15 | "resolveJsonModule": true, 16 | "esModuleInterop": true, 17 | "skipLibCheck": true, 18 | "experimentalDecorators": true, 19 | "lib": ["esnext", "dom"], 20 | }, 21 | "include": ["src/**/*"], 22 | "exclude": ["node_modules/**"] 23 | } 24 | 25 | -------------------------------------------------------------------------------- /examples/using-local-bundle/src/main.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * copy generated bundles into other directories and import it directly 3 | * 把生成的产物直接拷贝到项目并使用 4 | */ 5 | // if use import syntax(如果使用的是import语法) 6 | import './lib/index.js' 7 | 8 | // or if use require(如果使用的是require)——AMD/Commonjs 9 | // require('./lib/index.umd.js') 10 | 11 | /** 12 | * or left the local component directory as-is, vite will resolve the package.json 13 | * in the directory and find the proper entry for you automatically 14 | * 或者保持本地的component目录不动,vite会解析目录下的package.json并自动找到合适的入口文件 15 | */ 16 | // if use import syntax(如果使用的是import语法) 17 | // import '../../component' 18 | 19 | // or if use require(如果使用的是require)——AMD/Commonjs 20 | // require('../../component') 21 | -------------------------------------------------------------------------------- /examples/using-local-package/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "strict": false, 7 | "jsx": "react", 8 | "jsxFactory": "QuarkElement.h", 9 | "jsxFragmentFactory": "QuarkElement.Fragment", 10 | "sourceMap": false, 11 | "declaration": true, 12 | "declarationDir": "lib/types", 13 | "emitDeclarationOnly": true, 14 | "removeComments": true, 15 | "resolveJsonModule": true, 16 | "esModuleInterop": true, 17 | "skipLibCheck": true, 18 | "experimentalDecorators": true, 19 | "lib": ["esnext", "dom"] 20 | }, 21 | "include": ["src/**/*"], 22 | "exclude": ["node_modules/**"] 23 | } 24 | -------------------------------------------------------------------------------- /packages/create-quarkc/template-quarkc-component-ts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "strict": false, 7 | "jsx": "react", 8 | "jsxFactory": "QuarkElement.h", 9 | "jsxFragmentFactory": "QuarkElement.Fragment", 10 | "sourceMap": false, 11 | "declaration": true, 12 | "declarationDir": "lib/types", 13 | "emitDeclarationOnly": true, 14 | "removeComments": true, 15 | "resolveJsonModule": true, 16 | "esModuleInterop": true, 17 | "skipLibCheck": true, 18 | "experimentalDecorators": true, 19 | "lib": ["esnext", "dom"] 20 | }, 21 | "include": ["src/**/*"], 22 | "exclude": ["node_modules/**"] 23 | } 24 | -------------------------------------------------------------------------------- /packages/create-quarkc/template-quarkc-component/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "strict": false, 7 | "jsx": "react", 8 | "jsxFactory": "QuarkElement.h", 9 | "jsxFragmentFactory": "QuarkElement.Fragment", 10 | "sourceMap": false, 11 | "declaration": true, 12 | "declarationDir": "lib/types", 13 | "emitDeclarationOnly": true, 14 | "removeComments": true, 15 | "resolveJsonModule": true, 16 | "esModuleInterop": true, 17 | "skipLibCheck": true, 18 | "experimentalDecorators": true, 19 | "lib": ["esnext", "dom"] 20 | }, 21 | "include": ["src/**/*"], 22 | "exclude": ["node_modules/**"] 23 | } 24 | -------------------------------------------------------------------------------- /packages/create-quarkc/template-quarkc-component-ts/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Hello Quark 6 | 7 | 20 | 21 | 22 | 23 | 24 |

Edit src/index.tsx and save to test HMR

25 |

在 Vue.js 中调试(Debugging in Vue.js)

26 | 27 | 28 | -------------------------------------------------------------------------------- /packages/create-quarkc/template-quarkc-component/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Hello Quark 6 | 7 | 20 | 21 | 22 | 23 | 24 |

Edit src/index.tsx and save to test HMR

25 |

在 Vue.js 中调试(Debugging in Vue.js)

26 | 27 | 28 | -------------------------------------------------------------------------------- /demo4gluang/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + Quark 8 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |

Edit src/index.tsx and save to test HMR

25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /packages/router/vite.config.build.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import dts from 'vite-plugin-dts'; // 自动生成 .d.ts 类型声明文件 3 | 4 | export default defineConfig({ 5 | plugins: [ 6 | dts({ 7 | outDir: 'lib/types', 8 | }), 9 | ], 10 | build: { 11 | lib: { 12 | entry: [ 13 | "./src/index.ts", 14 | "./src/router.ts", 15 | "./src/routes.ts", 16 | "./src/quark-link.tsx", 17 | ], 18 | formats: ['es'], 19 | }, 20 | rollupOptions: { 21 | external: [ 22 | '@babel/runtime', 23 | 'quarkc', 24 | ], 25 | output: { 26 | dir: "lib", 27 | globals: { 28 | quarkc: 'Quarkc', 29 | }, 30 | }, 31 | plugins: [ 32 | ], 33 | }, 34 | }, 35 | }); 36 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + Quark 8 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |

Edit src/index.tsx and save to test HMR

25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /examples/using-local-bundle/README.en-US.md: -------------------------------------------------------------------------------- 1 | [简体中文](./README.md) | English 2 | 3 | # use local Quarkc component bundle example 4 | 5 | ## run 6 | 7 | ```bash 8 | npm i 9 | # important: make sure you've installed quarkc before using Quarkc component 10 | # in the projects generated with quark-cli's component template, by default quarkc will be externalized and excluded from bundle 11 | # it's meant to avoid including different versions of quarkc in distributed components, causing bundler's tree-shaking cannot work as expected, 12 | # to reduce the app's final bundle size 13 | npm i quarkc --save 14 | npm start 15 | ``` 16 | 17 | ## extra description 18 | The `src/lib` directory is directly copied from the component example (the `lib` directory generated under component example with command `npm run build`)。 19 | -------------------------------------------------------------------------------- /packages/create-quarkc/template-quarkc-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "quark-app", 3 | "version": "0.0.1", 4 | "scripts": { 5 | "dev": "npm run start", 6 | "start": "vite --open --force", 7 | "build": "rm -rf dist && vite build" 8 | }, 9 | "devDependencies": { 10 | "@babel/plugin-transform-runtime": "^7.19.6", 11 | "@rollup/plugin-babel": "^6.0.2", 12 | "@rollup/plugin-commonjs": "^23.0.2", 13 | "@rollup/plugin-node-resolve": "^15.0.1", 14 | "@rollup/plugin-typescript": "^9.0.2", 15 | "less": "^4.1.3", 16 | "rollup": "^3.2.3", 17 | "rollup-plugin-filesize": "^9.1.2", 18 | "vite": "^4.2.1" 19 | }, 20 | "engines": { 21 | "node": ">=12.0.0" 22 | }, 23 | "dependencies": { 24 | "@vaadin/router": "^1.7.5", 25 | "quarkc": "^1.0.40" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/create-quarkc/template-quarkc-app-ts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "quark-app", 3 | "version": "0.0.1", 4 | "scripts": { 5 | "dev": "npm run start", 6 | "start": "vite --open --force", 7 | "build": "rm -rf dist && vite build" 8 | }, 9 | "devDependencies": { 10 | "@babel/plugin-transform-runtime": "^7.19.6", 11 | "@rollup/plugin-babel": "^6.0.2", 12 | "@rollup/plugin-commonjs": "^23.0.2", 13 | "@rollup/plugin-node-resolve": "^15.0.1", 14 | "@rollup/plugin-typescript": "^9.0.2", 15 | "less": "^4.1.3", 16 | "rollup": "^3.2.3", 17 | "rollup-plugin-filesize": "^9.1.2", 18 | "vite": "^4.2.1" 19 | }, 20 | "engines": { 21 | "node": ">=12.0.0" 22 | }, 23 | "dependencies": { 24 | "@vaadin/router": "^1.7.5", 25 | "quarkc": "^1.0.40" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/create-quarkc/template-quarkc-component-ts/vite.config.build.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from "path"; 2 | import { defineConfig } from "vite"; 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | build: { 7 | lib: { 8 | entry: resolve("./src/index.tsx"), 9 | formats: ["es", "umd"], // 打包输出格式,默认输出 esm/umd 10 | fileName: (format, entryName) => { 11 | if (format === "es") { 12 | return `${entryName}.js`; 13 | } 14 | 15 | return `${entryName}.${format}.js`; 16 | }, 17 | name: "MyComponent", 18 | }, 19 | rollupOptions: { 20 | external: ["quarkc"], // 可选项,是否将 quarkc 打包进组件 21 | output: { 22 | dir: "lib", 23 | globals: { 24 | quarkc: "Quarkc", 25 | }, 26 | }, 27 | }, 28 | }, 29 | }); 30 | -------------------------------------------------------------------------------- /packages/create-quarkc/template-quarkc-component/vite.config.build.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from "path"; 2 | import { defineConfig } from "vite"; 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | build: { 7 | lib: { 8 | entry: resolve("./src/index.tsx"), 9 | formats: ["es", "umd"], // 打包输出格式,默认输出 esm/umd 10 | fileName: (format, entryName) => { 11 | if (format === "es") { 12 | return `${entryName}.js`; 13 | } 14 | 15 | return `${entryName}.${format}.js`; 16 | }, 17 | name: "MyComponent", 18 | }, 19 | rollupOptions: { 20 | external: ["quarkc"], // 可选项,是否将 quarkc 打包进组件 21 | output: { 22 | dir: "lib", 23 | globals: { 24 | quarkc: "Quarkc", 25 | }, 26 | }, 27 | }, 28 | }, 29 | }); 30 | -------------------------------------------------------------------------------- /demo4gluang/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "", 4 | "rootDir": "", 5 | "target": "esnext", 6 | "module": "esnext", 7 | "moduleResolution": "node", 8 | "strict": true, 9 | "jsx": "react", 10 | "jsxFactory": "QuarkElement.h", 11 | "jsxFragmentFactory": "QuarkElement.Fragment", 12 | "sourceMap": false, 13 | "declaration": false, 14 | "removeComments": true, 15 | "resolveJsonModule": true, 16 | "esModuleInterop": true, 17 | "skipLibCheck": true, 18 | "experimentalDecorators": true, 19 | "noImplicitAny": false, 20 | "lib": ["esnext", "dom"], 21 | // "outDir": "./tsc/test", 22 | "types": ["vite/client"], 23 | "paths": { 24 | "@/*": ["src/*"] 25 | } 26 | }, 27 | "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "typings"], 28 | "exclude": ["node_modules"], 29 | } 30 | -------------------------------------------------------------------------------- /packages/create-quarkc/APP.dev.md: -------------------------------------------------------------------------------- 1 | # 构建 Web 应用 2 | 3 | 创建不依赖任何前端框架(Vue/React等)的 **独立 Web 应用(Bate)** 4 | 5 | 6 | ## 安装并创建 7 | 8 | ```js 9 | npm create quarkc@latest 10 | // 选择 app... 11 | ``` 12 | 13 | ## 启动 14 | 15 | ``` 16 | npm start 17 | ``` 18 | 19 | ## 构建 20 | 21 | ``` 22 | npm run build 23 | ``` 24 | 25 | ## 特性 26 | 27 | - 无前端技术框架,能作为页面直接在其它框架(React、Vue等)项目中直接作为页面被使用 28 | - 体积极小,性能极高(秒开) 29 | - Web Components, Simple, Fast! 30 | - 高性能设计,Shadow DOM 与 Virtual DOM 融合 31 | 32 | 33 | ## 文档 34 | 35 | 完整文档,请访问 [quarkc.hellobike.com](https://quarkc.hellobike.com) 36 | 37 | 38 | 39 | ## 优秀案例 40 | 41 | | 作者 | github 地址 | 截图 / 链接 42 | | ---- | ---- | ----- | 43 | | @xsf0105 | https://github.com/hellof2e/quark-doc-home | ![1686575964690](https://github.com/hellof2e/quark/assets/14307551/9618427c-916b-4dfd-b28b-0e8e0f6ce744) | 44 | -------------------------------------------------------------------------------- /packages/create-quarkc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "create-quarkc", 3 | "version": "1.4.0", 4 | "type": "module", 5 | "license": "MIT", 6 | "author": "xsf0105", 7 | "bin": { 8 | "create-quarkc": "index.js", 9 | "cva": "index.js" 10 | }, 11 | "files": [ 12 | "index.js", 13 | "template-*", 14 | "dist" 15 | ], 16 | "scripts": { 17 | "dev": "unbuild --stub", 18 | "build": "unbuild", 19 | "typecheck": "tsc --noEmit", 20 | "prepublishOnly": "npm run build" 21 | }, 22 | "engines": { 23 | "node": "^14.18.0 || >=16.0.0" 24 | }, 25 | "repository": "https://github.com/hellof2e/quark-cli", 26 | "devDependencies": { 27 | "@types/cross-spawn": "^6.0.2", 28 | "@types/minimist": "^1.2.2", 29 | "@types/prompts": "^2.4.4", 30 | "cross-spawn": "^7.0.3", 31 | "kolorist": "^1.8.0", 32 | "minimist": "^1.2.8", 33 | "prompts": "^2.4.2" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /demo/src/pages/child-second.tsx: -------------------------------------------------------------------------------- 1 | import { QuarkElement, customElement } from "quarkc" 2 | import { Routes } from "quark-router" 3 | import style from "./style.less?inline" 4 | 5 | declare global { 6 | interface HTMLElementTagNameMap { 7 | "child-second": Child2; 8 | } 9 | } 10 | 11 | @customElement({ tag: "child-second", style }) 12 | class Child2 extends QuarkElement { 13 | private _routes = new Routes(this, [ 14 | {path: '1', render: () =>
child2-1 content
}, 15 | {path: '2', render: () =>
child2-2 content
}, 16 | ]) 17 | 18 | componentDidMount() { 19 | // console.log('dom loaded!', 'child-second') 20 | } 21 | 22 | render() { 23 | return ( 24 | <> 25 | child2 content 26 |
    27 |
  • child/2/2
  • 28 |
29 | { this._routes.outlet() } 30 | 31 | ); 32 | } 33 | } -------------------------------------------------------------------------------- /packages/core/src/core/util.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Checks if `value` is a `function`. 3 | * @param value The value to check 4 | * @returns Returns `true` if `value` is a function, else `false`. 5 | */ 6 | export function isFunction(value: any): value is (...args: any[]) => any { 7 | return typeof value === "function"; 8 | } 9 | 10 | export const slice = Array.prototype.slice; 11 | 12 | export const noop = () => {}; 13 | 14 | /** set dom attribute for consistent behavior */ 15 | export const updateDomAttr = (dom: HTMLElement, name: string, value: any) => { 16 | let isAria = false; 17 | 18 | if (value != null && (value !== false || (isAria = name.indexOf('aria-') === 0))) { 19 | dom.setAttribute( 20 | name, 21 | !isAria && typeof value === 'boolean' 22 | // true but not aria-* 23 | ? '' 24 | // falsy aria-* values 25 | : value 26 | ); 27 | } else { 28 | dom.removeAttribute(name); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /demo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "rootDir": "..", 5 | "target": "esnext", 6 | "module": "esnext", 7 | "moduleResolution": "node", 8 | "strict": true, 9 | "jsx": "react", 10 | "jsxFactory": "QuarkElement.h", 11 | "jsxFragmentFactory": "QuarkElement.Fragment", 12 | "sourceMap": false, 13 | "declaration": false, 14 | "removeComments": true, 15 | "resolveJsonModule": true, 16 | "esModuleInterop": true, 17 | "skipLibCheck": true, 18 | "experimentalDecorators": true, 19 | "noImplicitAny": false, 20 | "lib": ["esnext", "dom"], 21 | // "outDir": "./tsc/test", 22 | "types": ["vite/client"], 23 | "paths": { 24 | "@/*": ["src/*"], 25 | "quarkc": ["../packages/core"], 26 | "quark-router": ["../packages/router/src"] 27 | } 28 | }, 29 | "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "typings"], 30 | "exclude": ["./node_modules"], 31 | } 32 | -------------------------------------------------------------------------------- /packages/router/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noImplicitAny": false, 4 | "noImplicitThis": false, 5 | "baseUrl": ".", 6 | "rootDir": "./src", 7 | "target": "esnext", 8 | "module": "esnext", 9 | "moduleResolution": "node", 10 | "jsx": "react", 11 | "jsxFactory": "QuarkElement.h", 12 | "jsxFragmentFactory": "QuarkElement.Fragment", 13 | "strict": true, 14 | "sourceMap": false, 15 | "removeComments": false, 16 | "resolveJsonModule": false, 17 | "esModuleInterop": true, 18 | "skipLibCheck": true, 19 | "experimentalDecorators": true, 20 | "outDir": "lib", 21 | "declarationDir": "./lib", 22 | "declaration": true /* 生成相关的 '.d.ts' 文件。 */, 23 | "emitDeclarationOnly": true /* 只生成声明文件,不生成 js 文件*/, 24 | "paths": { 25 | "@/*": ["src/*"] 26 | } 27 | }, 28 | "include": ["src/**/*.ts", "src/**/*.tsx"], 29 | "exclude": ["node_modules"] 30 | } 31 | -------------------------------------------------------------------------------- /packages/create-quarkc/template-quarkc-component-ts/index.vue.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Test in Vue.js 6 | 16 | 17 | 18 |
19 | 20 |
21 | 22 |

Edit src/index.tsx and save to test HMR

23 | 24 | 25 | 38 | 39 | -------------------------------------------------------------------------------- /packages/create-quarkc/template-quarkc-component/index.vue.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Test in Vue.js 6 | 16 | 17 | 18 |
19 | 20 |
21 | 22 |

Edit src/index.tsx and save to test HMR

23 | 24 | 25 | 38 | 39 | -------------------------------------------------------------------------------- /packages/core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noImplicitAny": false, 4 | "noImplicitThis": false, 5 | "baseUrl": ".", 6 | "rootDir": ".", 7 | "target": "esnext", 8 | "module": "esnext", 9 | "moduleResolution": "node", 10 | "strict": true, 11 | "jsx": "react", 12 | "jsxFactory": "QuarkElement.h", 13 | "jsxFragmentFactory": "QuarkElement.Fragment", 14 | "sourceMap": false, 15 | "resolveJsonModule": true, 16 | "esModuleInterop": true, 17 | "skipLibCheck": true, 18 | "experimentalDecorators": true, 19 | "outDir": "lib", 20 | "declarationDir": "./lib", 21 | "declaration": true /* 生成相关的 '.d.ts' 文件。 */, 22 | "emitDeclarationOnly": true /* 只生成声明文件,不生成 js 文件*/, 23 | "lib": ["esnext", "dom"], 24 | "paths": { 25 | "@/*": ["src/*"] 26 | } 27 | }, 28 | "include": ["src/**/*.ts", "src/**/*.tsx", "test/**/*.ts", "test/**/*.tsx", "rollup.config.ts"], 29 | "exclude": ["node_modules"] 30 | } 31 | -------------------------------------------------------------------------------- /packages/create-quarkc/template-quarkc-component/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-component", 3 | "version": "0.0.1", 4 | "scripts": { 5 | "dev": "npm run start", 6 | "start": "vite --open", 7 | "build": "vite build --config vite.config.build.ts && tsc", 8 | "prepublishOnly": "npm run build" 9 | }, 10 | "type": "module", 11 | "main": "./lib/index.umd.js", 12 | "module": "./lib/index.js", 13 | "types": "./lib/types/index.d.ts", 14 | "exports": { 15 | ".": { 16 | "import": "./lib/index.js", 17 | "require": "./lib/index.umd.js", 18 | "types": "./lib/types/index.d.ts" 19 | } 20 | }, 21 | "keywords": [ 22 | "frontend", 23 | "quark", 24 | "web components" 25 | ], 26 | "dependencies": { 27 | "quarkc": "^1.0.40" 28 | }, 29 | "devDependencies": { 30 | "@types/node": "^20.2.5", 31 | "less": "^4.1.3", 32 | "tslib": "^2.5.3", 33 | "typescript": "^5.1.3", 34 | "vite": "^4.3.0" 35 | }, 36 | "engines": { 37 | "node": ">=14.18.0" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/create-quarkc/template-quarkc-component-ts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-component", 3 | "version": "0.0.1", 4 | "scripts": { 5 | "dev": "npm run start", 6 | "start": "vite --open", 7 | "build": "vite build --config vite.config.build.ts && tsc", 8 | "prepublishOnly": "npm run build" 9 | }, 10 | "type": "module", 11 | "main": "./lib/index.umd.js", 12 | "module": "./lib/index.js", 13 | "types": "./lib/types/index.d.ts", 14 | "exports": { 15 | ".": { 16 | "import": "./lib/index.js", 17 | "require": "./lib/index.umd.js", 18 | "types": "./lib/types/index.d.ts" 19 | } 20 | }, 21 | "keywords": [ 22 | "frontend", 23 | "quark", 24 | "web components" 25 | ], 26 | "dependencies": { 27 | "quarkc": "^1.0.40" 28 | }, 29 | "devDependencies": { 30 | "@types/node": "^20.2.5", 31 | "less": "^4.1.3", 32 | "tslib": "^2.5.3", 33 | "typescript": "^5.1.3", 34 | "vite": "^4.3.0" 35 | }, 36 | "engines": { 37 | "node": ">=14.18.0" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/core/src/eventController.ts: -------------------------------------------------------------------------------- 1 | import DblKeyMap from "./dblKeyMap"; 2 | 3 | export type EventHandler = (evt: Event) => void; 4 | 5 | export class EventController { 6 | private eventMap: DblKeyMap> = 7 | new DblKeyMap(); 8 | 9 | bindListener = ( 10 | el: Element | null, 11 | eventName: string, 12 | eventHandler: EventHandler 13 | ) => { 14 | if (!el || !eventName || !eventHandler) { 15 | return; 16 | } 17 | 18 | let list = this.eventMap.get(el, eventName); 19 | if (!list) { 20 | list = new Set(); 21 | this.eventMap.set(el, eventName, list); 22 | } 23 | if (!list.has(eventHandler)) { 24 | list.add(eventHandler); 25 | el.addEventListener(eventName, eventHandler, true); 26 | } 27 | }; 28 | 29 | removeAllListener = () => { 30 | this.eventMap.forEach((list, el, eventName) => { 31 | list.forEach((handler) => { 32 | el.removeEventListener(eventName, handler, true); 33 | }); 34 | }); 35 | this.eventMap.deleteAll(); 36 | }; 37 | } 38 | -------------------------------------------------------------------------------- /packages/create-quarkc/README.md: -------------------------------------------------------------------------------- 1 | # create-quarkc 2 | 3 | If you want to create cross-framework/technology stack components, or develop standalone web apps that don't depend on any front-end framework (Vue/React, etc.). 4 | 5 | English | [简体中文](./README.md) 6 | 7 | ## Scaffolding Your First Quarkc Project 8 | 9 | > **Compatibility Note:** 10 | > Quarkc requires [Node.js](https://nodejs.org/en/) version 14.18+, 16+. However, some templates require a higher Node.js version to work, please upgrade if your package manager warns about it. 11 | 12 | 13 | With NPM: 14 | 15 | ```bash 16 | $ npm create quarkc@latest 17 | ``` 18 | 19 | With Yarn: 20 | 21 | ```bash 22 | $ yarn create quarkc 23 | ``` 24 | 25 | With PNPM: 26 | 27 | ```bash 28 | $ pnpm create quarkc 29 | ``` 30 | 31 | Then follow the prompts! 32 | 33 | Currently supported template presets include: 34 | 35 | - `quarkc-app` 36 | - `quarkc-app-ts` 37 | - `quarkc-component` 38 | - `quarkc-component-ts` 39 | 40 | ### Documentation 41 | 42 | For full documentation, visit [quarkc.hellobike.com](https://quarkc.hellobike.com) -------------------------------------------------------------------------------- /packages/create-quarkc/README.en-US.md: -------------------------------------------------------------------------------- 1 | # create-quarkc 2 | 3 | If you want to create cross-framework/technology stack components, or develop standalone web apps that don't depend on any front-end framework (Vue/React, etc.). 4 | 5 | English | [简体中文](./README.md) 6 | 7 | ## Scaffolding Your First Quarkc Project 8 | 9 | > **Compatibility Note:** 10 | > Quarkc requires [Node.js](https://nodejs.org/en/) version 14.18+, 16+. However, some templates require a higher Node.js version to work, please upgrade if your package manager warns about it. 11 | 12 | 13 | With NPM: 14 | 15 | ```bash 16 | $ npm create quarkc@latest 17 | ``` 18 | 19 | With Yarn: 20 | 21 | ```bash 22 | $ yarn create quarkc 23 | ``` 24 | 25 | With PNPM: 26 | 27 | ```bash 28 | $ pnpm create quarkc 29 | ``` 30 | 31 | Then follow the prompts! 32 | 33 | Currently supported template presets include: 34 | 35 | - `quarkc-app` 36 | - `quarkc-app-ts` 37 | - `quarkc-component` 38 | - `quarkc-component-ts` 39 | 40 | ### Documentation 41 | 42 | For full documentation, visit [quarkc.hellobike.com](https://quarkc.hellobike.com) -------------------------------------------------------------------------------- /packages/core/src/dblKeyMap.ts: -------------------------------------------------------------------------------- 1 | export default class DblKeyMap { 2 | private map: Map> = new Map(); 3 | 4 | get(key: Key): Map | undefined 5 | get(key: Key, subKey: SubKey): Value 6 | 7 | get(key: Key, subKey?: SubKey) { 8 | const subMap = this.map.get(key); 9 | if (subMap) { 10 | if (!subKey) return subMap 11 | return subMap.get(subKey); 12 | } 13 | } 14 | 15 | set(key: Key, subKey: SubKey, value: Value) { 16 | let subMap = this.map.get(key); 17 | if (!subMap) { 18 | subMap = new Map(); 19 | this.map.set(key, subMap); 20 | } 21 | subMap?.set(subKey, value); 22 | } 23 | 24 | forEach(cb: (value: Value, key: Key, subKey: SubKey) => void) { 25 | this.map.forEach((subMap, key) => { 26 | subMap.forEach((value, subKey) => { 27 | cb(value, key, subKey); 28 | }); 29 | }); 30 | } 31 | 32 | delete(key: Key) { 33 | this.map.delete(key); 34 | } 35 | 36 | deleteAll() { 37 | this.map.forEach((_, key) => { 38 | this.map.delete(key); 39 | }); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 哈啰大前端 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/core/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 哈啰大前端 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/create-quarkc/template-quarkc-app/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 哈啰前端 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/create-quarkc/template-quarkc-app-ts/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 哈啰前端 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "quark", 3 | "private": true, 4 | "version": "1.0.0", 5 | "author": "hellof2e", 6 | "license": "MIT", 7 | "scripts": { 8 | "dev": "pnpm run build4core && pnpm run -C demo dev", 9 | "dev4gluang": "pnpm run build4core && pnpm run -C demo4gluang dev", 10 | "codeformat": "prettier --write *", 11 | "build": "pnpm -r --filter='./packages/create-quarkc' run build", 12 | "build4core": "pnpm run -C packages/core build" 13 | }, 14 | "devDependencies": { 15 | "@commitlint/cli": "^17.0.3", 16 | "@typescript-eslint/eslint-plugin": "^5.59.9", 17 | "@typescript-eslint/parser": "^5.59.9", 18 | "eslint": "^7.23.2", 19 | "eslint-define-config": "^1.21.0", 20 | "lint-staged": "^10.5.4", 21 | "prettier": "^2.0.0", 22 | "rimraf": "^3.0.2", 23 | "shelljs": "^0.8.5", 24 | "typescript": "^5.0.2", 25 | "less": "^4.1.3", 26 | "postcss": "^8.4.24", 27 | "unbuild": "^1.2.1", 28 | "vite": "^4.1.0" 29 | }, 30 | "packageManager": "pnpm@8.6.2+sha256.c6da9e00697e334b6193c034a5d1508e4c8605b12f249736b13f31139f4f0d73", 31 | "lint-staged": { 32 | "src/**/*.{ js,ts,json}": [ 33 | "git add" 34 | ] 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /examples/using-cdn/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Hello Quark 6 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 25 | 26 | 27 | 28 | 41 | 42 | -------------------------------------------------------------------------------- /packages/create-quarkc/template-quarkc-app-ts/src/main.ts: -------------------------------------------------------------------------------- 1 | import { Router } from '@vaadin/router' 2 | import "./main.less" 3 | 4 | import './views/NotFound' 5 | 6 | const outlet = document.querySelector('#root'); 7 | export const router = new Router(outlet); 8 | 9 | router.setRoutes([{ 10 | path: '/', 11 | component: 'app-home', // custom element name 12 | action: async () => { await import('./views/home/index'); } 13 | }, { 14 | path: '/sub', 15 | component: 'app-sub', 16 | action: async () => { await import('./views/sub'); } 17 | }, { 18 | path: '(.*)', 19 | component: 'app-not-found' 20 | } 21 | ]) 22 | 23 | // router.setRoutes([ 24 | // { path: '/', component: 'hello-home' }, 25 | // { path: '/a', component: 'hello-a' }, 26 | // { path: '/b', component: 'hello-b' }, 27 | // { 28 | // path: '/nav', 29 | // component: 'hello-nav', 30 | // children: [ 31 | // { path: '/a', component: 'hello-a' }, 32 | // { path: '/b', component: 'hello-b' }, 33 | // { path: '(.*)', component: 'hello-not-found' }, 34 | // ], 35 | // }, 36 | // { path: '/fruit/:id', component: 'hello-fruit' }, 37 | // { path: '(.*)', component: 'hello-not-found' }, 38 | // ]); -------------------------------------------------------------------------------- /packages/create-quarkc/template-quarkc-app/src/main.ts: -------------------------------------------------------------------------------- 1 | import { Router } from '@vaadin/router' 2 | import "./main.less" 3 | 4 | import './views/NotFound' 5 | 6 | const outlet = document.querySelector('#root'); 7 | export const router = new Router(outlet); 8 | 9 | router.setRoutes([{ 10 | path: '/', 11 | component: 'app-home', // custom element name 12 | action: async () => { await import('./views/home/index'); } 13 | }, { 14 | path: '/sub', 15 | component: 'app-sub', 16 | action: async () => { await import('./views/sub'); } 17 | }, { 18 | path: '(.*)', 19 | component: 'app-not-found' 20 | } 21 | ]) 22 | 23 | // router.setRoutes([ 24 | // { path: '/', component: 'hello-home' }, 25 | // { path: '/a', component: 'hello-a' }, 26 | // { path: '/b', component: 'hello-b' }, 27 | // { 28 | // path: '/nav', 29 | // component: 'hello-nav', 30 | // children: [ 31 | // { path: '/a', component: 'hello-a' }, 32 | // { path: '/b', component: 'hello-b' }, 33 | // { path: '(.*)', component: 'hello-not-found' }, 34 | // ], 35 | // }, 36 | // { path: '/fruit/:id', component: 'hello-fruit' }, 37 | // { path: '(.*)', component: 'hello-not-found' }, 38 | // ]); -------------------------------------------------------------------------------- /packages/create-quarkc/template-quarkc-component-ts/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { QuarkElement, property, customElement } from "quarkc" 2 | import style from "./index.less?inline" 3 | 4 | @customElement({ tag: "my-component", style }) 5 | class MyComponent extends QuarkElement { 6 | @property({ type: Number }) // 外部属性 7 | count = 0 8 | 9 | @property({ type: String }) 10 | text = '' 11 | 12 | add = () => { 13 | // 内部事件 14 | this.count += 1 15 | }; 16 | 17 | componentDidMount() { 18 | // 生命周期 19 | // console.log("dom loaded!") 20 | // ... 21 | } 22 | 23 | render() { 24 | return ( 25 | <> 26 |
27 | 28 | 33 | 34 |
35 | 36 |

Quark - {this.text}

37 | 38 |
39 | 40 |
41 | 42 | ); 43 | } 44 | } 45 | 46 | declare global { 47 | interface HTMLElementTagNameMap { 48 | "my-component": MyComponent 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /packages/create-quarkc/template-quarkc-component/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { QuarkElement, property, customElement } from "quarkc" 2 | import style from "./index.less?inline" 3 | 4 | declare global { 5 | interface HTMLElementTagNameMap { 6 | "my-component": MyComponent; 7 | } 8 | } 9 | 10 | @customElement({ tag: "my-component", style }) 11 | class MyComponent extends QuarkElement { 12 | @property({ type: Number }) // 外部属性 13 | count = 0 14 | 15 | @property({ type: String }) 16 | text = '' 17 | 18 | add = () => { 19 | // 内部事件 20 | this.count += 1 21 | }; 22 | 23 | componentDidMount() { 24 | // 生命周期 25 | // console.log("dom loaded!") 26 | // ... 27 | } 28 | 29 | render() { 30 | return ( 31 | <> 32 |
33 | 34 | 39 | 40 |
41 | 42 |

Quark - {this.text}

43 | 44 |
45 | 46 |
47 | 48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /packages/core/src/core/diff/catch-error.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Find the closest error boundary to a thrown error and call it 3 | * @param {object} error The thrown value 4 | * @param vnode The vnode that threw 5 | * the error that was caught (except for unmounting when this parameter 6 | * is the highest parent that was being unmounted) 7 | * @param oldVNode 8 | * @param errorInfo 9 | */ 10 | export function _catchError(error, vnode, oldVNode, errorInfo) { 11 | let component, ctor, handled; 12 | 13 | for (; (vnode = vnode._parent);) { 14 | if ((component = vnode._component) && !component._processingException) { 15 | try { 16 | ctor = component.constructor; 17 | 18 | if (ctor && ctor.getDerivedStateFromError != null) { 19 | Object.assign(component, ctor.getDerivedStateFromError(error)) 20 | handled = component._dirty; 21 | } 22 | 23 | if (component.componentDidCatch != null) { 24 | component.componentDidCatch(error, errorInfo || {}); 25 | handled = component._dirty; 26 | } 27 | 28 | // This is an error boundary. Mark it as having bailed out, and whether it was mid-hydration. 29 | if (handled) { 30 | return (component._pendingError = component); 31 | } 32 | } catch (e) { 33 | error = e; 34 | } 35 | } 36 | } 37 | 38 | throw error; 39 | } 40 | -------------------------------------------------------------------------------- /packages/router/src/eventEmitter.ts: -------------------------------------------------------------------------------- 1 | type CustomListener = (eventData: T) => void; 2 | 3 | class EventEmitter { 4 | private events: Map>>; 5 | 6 | constructor() { 7 | this.events = new Map(); 8 | } 9 | 10 | // 订阅事件 11 | on(eventName: string, listener: CustomListener): void { 12 | if (!this.events.has(eventName)) { 13 | this.events.set(eventName, new Set()); 14 | } 15 | this.events.get(eventName)!.add(listener); 16 | } 17 | 18 | // 触发事件 19 | emit(eventName: string, eventData: T): void { 20 | if (this.events.has(eventName)) { 21 | for (const listener of this.events.get(eventName)!) { 22 | listener(eventData); 23 | } 24 | } 25 | } 26 | 27 | // 取消订阅 28 | off(eventName: string, listener: CustomListener): void { 29 | if (this.events.has(eventName)) { 30 | this.events.get(eventName)!.delete(listener); 31 | } 32 | } 33 | 34 | // 只订阅一次 35 | once(eventName: string, listener: CustomListener): void { 36 | const onceWrapper: CustomListener = (eventData) => { 37 | this.off(eventName, onceWrapper); 38 | listener(eventData); 39 | }; 40 | this.on(eventName, onceWrapper); 41 | } 42 | } 43 | 44 | export const eventBus = new EventEmitter(); 45 | 46 | export default EventEmitter; -------------------------------------------------------------------------------- /packages/create-quarkc/template-quarkc-app-ts/src/views/home/index.tsx: -------------------------------------------------------------------------------- 1 | import { QuarkElement, customElement, state, createRef, } from "quarkc" 2 | import style from "./index.less?inline" 3 | 4 | @customElement({ tag: "app-home", style }) 5 | class Home extends QuarkElement { 6 | // https://quarkc.hellobike.com/#/zh-CN/docs/properties 7 | @state() 8 | msg = 'Welcome to Your Quark App' 9 | 10 | // https://quarkc.hellobike.com/#/zh-CN/docs/lifecycle 11 | // componentDidMount() {} 12 | // shouldComponentUpdate() {} 13 | // componentDidUpdate() {} 14 | // componentWillUnmount() {} 15 | 16 | render() { 17 | return ( 18 | <> 19 |
20 | quark logo 21 |

{ this.msg }

22 |

23 | For a guide and recipes on how to configure / customize this project,
24 | check out the 25 | Quarkc documentation. 26 |

27 |

Route

28 | 32 |
33 | 34 | ) 35 | } 36 | } -------------------------------------------------------------------------------- /packages/create-quarkc/template-quarkc-app/src/views/home/index.tsx: -------------------------------------------------------------------------------- 1 | import { QuarkElement, customElement, state, createRef, } from "quarkc" 2 | import style from "./index.less?inline" 3 | 4 | @customElement({ tag: "app-home", style }) 5 | class Home extends QuarkElement { 6 | // https://quarkc.hellobike.com/#/zh-CN/docs/properties 7 | @state() 8 | msg = 'Welcome to Your Quark App' 9 | 10 | // https://quarkc.hellobike.com/#/zh-CN/docs/lifecycle 11 | // componentDidMount() {} 12 | // shouldComponentUpdate() {} 13 | // componentDidUpdate() {} 14 | // componentWillUnmount() {} 15 | 16 | render() { 17 | return ( 18 | <> 19 |
20 | quark logo 21 |

{ this.msg }

22 |

23 | For a guide and recipes on how to configure / customize this project,
24 | check out the 25 | Quarkc documentation. 26 |

27 |

Route

28 | 32 |
33 | 34 | ) 35 | } 36 | } -------------------------------------------------------------------------------- /packages/core/src/core/render.js: -------------------------------------------------------------------------------- 1 | import { EMPTY_OBJ } from './constants'; 2 | import { diff } from './diff'; 3 | import { createElement, Fragment } from './create-element'; 4 | import options from './options'; 5 | import { slice } from './util'; 6 | 7 | /** 8 | * Render a quark virtual node into a DOM element 9 | * @param vnode The virtual node to render 10 | * @param root The root DOM element to render into 11 | */ 12 | export function render(vnode, root) { 13 | if (options._root) options._root(vnode, root); 14 | 15 | // To be able to support calling `render()` multiple times on the same 16 | // DOM node, we need to obtain a reference to the previous tree. We do 17 | // this by assigning a new `_children` property to DOM nodes which points 18 | // to the last rendered tree. By default this property is not present, which 19 | // means that we are mounting a new tree for the first time. 20 | let oldVNode = root._children; 21 | 22 | vnode = root._children = createElement(Fragment, null, [vnode]); 23 | 24 | diff( 25 | root, 26 | // Determine the new vnode tree and store it on the DOM element on 27 | // our custom `_children` property. 28 | vnode, 29 | oldVNode || EMPTY_OBJ, 30 | root.ownerSVGElement !== undefined, 31 | oldVNode 32 | ? null 33 | : root.firstChild 34 | ? slice.call(root.childNodes) 35 | : null, 36 | oldVNode 37 | ? oldVNode._dom 38 | : root.firstChild, 39 | ); 40 | } -------------------------------------------------------------------------------- /packages/core/src/models.ts: -------------------------------------------------------------------------------- 1 | type TypeHint = typeof Boolean | typeof Number | typeof String; 2 | export type converterFunction = (val: string | null, type?: TypeHint) => any; 3 | 4 | export interface PropertyDeclaration { 5 | /** 6 | * Whether the property is Reactive and watches for the attribute change. It will be added to {@link https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#responding_to_attribute_changes observedAttributes} automatically 7 | * 8 | * 是否响应式属性,接收外部的参数变化,会自动加入{@link https://developer.mozilla.org/zh-CN/docs/Web/API/Web_components/Using_custom_elements#%E5%93%8D%E5%BA%94%E5%B1%9E%E6%80%A7%E5%8F%98%E5%8C%96 observedAttributes}数组中 9 | */ 10 | readonly observed?: boolean | string; 11 | /** 12 | * Specify the type of the property, 13 | * and then quarkc will transform string attribute value to specified type automatically. 14 | * 15 | * Boolean property's default value will be ignored (defaults to 'false'), to line up with HTML standard. 16 | * 17 | * 属性类型,会针对类型做不同的特殊处理。Boolean, Number, String 18 | * 19 | * 布尔值属性设置的默认值会被忽略(默认值为false) 20 | * 21 | */ 22 | readonly type?: TypeHint; 23 | /** 24 | * 从外部获取属性时的值转换方法 25 | */ 26 | readonly converter?: converterFunction; 27 | /** 28 | * 创建内部属性名和外部属性名不同时,可以通过改属性指定外部属性名称 29 | */ 30 | readonly attribute?: string; 31 | /** 是否为组件内部属性——不作为HTML属性传递 */ 32 | readonly internal?: boolean; 33 | } 34 | -------------------------------------------------------------------------------- /packages/create-quarkc/template-quarkc-component/src/index.less: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: Inter, Avenir, Helvetica, Arial, sans-serif; 3 | font-size: 16px; 4 | font-weight: 400; 5 | color-scheme: light dark; 6 | color: rgba(255, 255, 255, 0.87); 7 | background-color: #242424; 8 | } 9 | 10 | :host { 11 | display: block; 12 | margin: 0 auto; 13 | padding: 2rem; 14 | text-align: center; 15 | border: 2px solid #0da6e9; 16 | width: 500px; 17 | } 18 | 19 | .logo { 20 | height: 6em; 21 | padding: 1.5em; 22 | will-change: filter; 23 | 24 | &:hover { 25 | filter: drop-shadow(0 0 2em #0da6e9aa); 26 | transition: all 0.5s; 27 | } 28 | } 29 | 30 | .card { 31 | padding: 2em; 32 | } 33 | 34 | button { 35 | border-radius: 8px; 36 | border: 1px solid transparent; 37 | padding: 0.6em 1.2em; 38 | font-size: 1em; 39 | font-weight: 500; 40 | font-family: inherit; 41 | cursor: pointer; 42 | transition: border-color 0.25s; 43 | 44 | &:hover { 45 | border-color: #0da6e9; 46 | } 47 | 48 | &:focus, 49 | &:focus-visible { 50 | outline: 4px auto -webkit-focus-ring-color; 51 | } 52 | } 53 | 54 | .read-the-docs { 55 | color: #888; 56 | } 57 | 58 | @media (prefers-color-scheme: light) { 59 | :root { 60 | color: #213547; 61 | background-color: #ffffff; 62 | } 63 | 64 | a:hover { 65 | color: #747bff; 66 | } 67 | 68 | button { 69 | background-color: #f9f9f9; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /packages/create-quarkc/template-quarkc-component-ts/src/index.less: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: Inter, Avenir, Helvetica, Arial, sans-serif; 3 | font-size: 16px; 4 | font-weight: 400; 5 | color-scheme: light dark; 6 | color: rgba(255, 255, 255, 0.87); 7 | background-color: #242424; 8 | } 9 | 10 | :host { 11 | display: block; 12 | margin: 0 auto; 13 | padding: 2rem; 14 | text-align: center; 15 | border: 2px solid #0da6e9; 16 | width: 500px; 17 | } 18 | 19 | .logo { 20 | height: 6em; 21 | padding: 1.5em; 22 | will-change: filter; 23 | 24 | &:hover { 25 | filter: drop-shadow(0 0 2em #0da6e9aa); 26 | transition: all 0.5s; 27 | } 28 | } 29 | 30 | .card { 31 | padding: 2em; 32 | } 33 | 34 | button { 35 | border-radius: 8px; 36 | border: 1px solid transparent; 37 | padding: 0.6em 1.2em; 38 | font-size: 1em; 39 | font-weight: 500; 40 | font-family: inherit; 41 | cursor: pointer; 42 | transition: border-color 0.25s; 43 | 44 | &:hover { 45 | border-color: #0da6e9; 46 | } 47 | 48 | &:focus, 49 | &:focus-visible { 50 | outline: 4px auto -webkit-focus-ring-color; 51 | } 52 | } 53 | 54 | .read-the-docs { 55 | color: #888; 56 | } 57 | 58 | @media (prefers-color-scheme: light) { 59 | :root { 60 | color: #213547; 61 | background-color: #ffffff; 62 | } 63 | 64 | a:hover { 65 | color: #747bff; 66 | } 67 | 68 | button { 69 | background-color: #f9f9f9; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /demo/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /demo4gluang/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/router/src/utils.ts: -------------------------------------------------------------------------------- 1 | const encodeReserveRE = /[!'()*]/g 2 | const encodeReserveReplacer = (c) => "%" + c.charCodeAt(0).toString(16) 3 | const commaRE = /%2C/g 4 | 5 | // fixed encodeURIComponent which is more conformant to RFC3986: 6 | // - escapes [!'()*] 7 | // - preserve commas 8 | const encode = (str: string) => 9 | encodeURIComponent(str) 10 | .replace(encodeReserveRE, encodeReserveReplacer) 11 | .replace(commaRE, ",") 12 | 13 | export function stringifyQuery(obj: { [name: string]: string }): string { 14 | const res = obj 15 | ? Object.keys(obj) 16 | .map((key) => { 17 | const val = obj[key] 18 | 19 | if (val === undefined) { 20 | return "" 21 | } 22 | 23 | if (val === null) { 24 | return encode(key) 25 | } 26 | 27 | if (Array.isArray(val)) { 28 | const result = [] as string[] 29 | val.forEach((val2) => { 30 | if (val2 === undefined) { 31 | return 32 | } 33 | if (val2 === null) { 34 | result.push(encode(key)) 35 | } else { 36 | result.push(encode(key) + "=" + encode(val2)) 37 | } 38 | }) 39 | return result.join("&") 40 | } 41 | 42 | return encode(key) + "=" + encode(val) 43 | }) 44 | .filter((x) => x.length > 0) 45 | .join("&") 46 | : null 47 | return res ? `?${res}` : "" 48 | } 49 | -------------------------------------------------------------------------------- /demo/src/index.less: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: Inter, Avenir, Helvetica, Arial, sans-serif; 3 | font-size: 16px; 4 | font-weight: 400; 5 | color-scheme: light dark; 6 | color: rgba(255, 255, 255, 0.87); 7 | } 8 | 9 | :host .main { 10 | margin: 0 auto; 11 | padding: 2rem; 12 | text-align: center; 13 | border: 2px solid #0da6e9; 14 | width: 500px; 15 | } 16 | 17 | .logo { 18 | height: 6em; 19 | padding: 1.5em; 20 | will-change: filter; 21 | 22 | &:hover { 23 | filter: drop-shadow(0 0 2em #0da6e9aa); 24 | transition: all 0.5s; 25 | } 26 | } 27 | 28 | .card { 29 | padding: 2em; 30 | } 31 | 32 | button { 33 | border-radius: 8px; 34 | border: 1px solid transparent; 35 | padding: 0.6em 1.2em; 36 | font-size: 1em; 37 | font-weight: 500; 38 | font-family: inherit; 39 | background-color: #1a1a1a; 40 | cursor: pointer; 41 | transition: border-color 0.25s; 42 | color: white; 43 | &:hover { 44 | border-color: #0da6e9; 45 | } 46 | 47 | &:focus, 48 | &:focus-visible { 49 | outline: 4px auto -webkit-focus-ring-color; 50 | } 51 | } 52 | 53 | .read-the-docs { 54 | color: #888; 55 | } 56 | 57 | @media (prefers-color-scheme: light) { 58 | :root { 59 | color: #213547; 60 | background-color: #ffffff; 61 | } 62 | 63 | a:hover { 64 | color: #747bff; 65 | } 66 | 67 | button { 68 | color: #213547; 69 | background-color: #f9f9f9; 70 | } 71 | } 72 | 73 | :host { 74 | --quark-link-text-decoration: underline; 75 | --quark-link-color: blue; 76 | } -------------------------------------------------------------------------------- /.github/workflows/release-for-create-quarkc.yml: -------------------------------------------------------------------------------- 1 | name: Release Create Quarkc 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | paths: 7 | - "packages/create-quarkc/package.json" 8 | 9 | jobs: 10 | release: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | with: 15 | fetch-depth: 0 16 | 17 | - name: Setup node 18 | uses: actions/setup-node@v2 19 | with: 20 | node-version: "16" 21 | registry-url: https://registry.npmjs.org/ 22 | 23 | - name: Install pnpm 24 | uses: pnpm/action-setup@v2 25 | with: 26 | version: 8 27 | run_install: false 28 | 29 | # Use cache to reduce installation time 30 | - name: Get pnpm store directory 31 | id: pnpm-cache 32 | shell: bash 33 | run: | 34 | echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT 35 | 36 | - uses: actions/cache@v3 37 | name: Setup pnpm cache 38 | with: 39 | path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} 40 | key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} 41 | restore-keys: | 42 | ${{ runner.os }}-pnpm-store- 43 | 44 | - name: Install dependencies 45 | run: pnpm install 46 | 47 | - name: Build 48 | run: pnpm run build 49 | 50 | - name: publish 51 | working-directory: "packages/create-quarkc" 52 | env: 53 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 54 | run: npm publish 55 | -------------------------------------------------------------------------------- /demo4gluang/src/app-header/index.tsx: -------------------------------------------------------------------------------- 1 | import { QuarkElement, customElement, state, watch, createRef } from "quarkc" 2 | import style from "./index.less?inline" 3 | import suntzu from "./suntzu.jpeg" 4 | 5 | // 链接全局store 6 | import { connectStore } from '../gluang'; 7 | // 使用全家状态/方法 8 | import { store } from '../store'; 9 | 10 | 11 | @customElement({ tag: "app-header", style }) 12 | class MyComponent extends connectStore(QuarkElement) { 13 | @state() 14 | showAuthorName = false 15 | 16 | @watch('showAuthorName') 17 | handleShowChange(newVal) { 18 | // console.log('handleShowChange', newVal) 19 | store.author = newVal ? 'Sun Tzu' : 'Guess who?'; 20 | this.$nextTick(() => { 21 | const { current: btn } = this.btn; 22 | if (btn) { 23 | // console.log('nextTick, content of btn:', btn.textContent) 24 | } 25 | }) 26 | } 27 | 28 | handleSwitch = () => { 29 | this.showAuthorName = !this.showAuthorName; 30 | } 31 | 32 | componentDidUpdate(propName, oldVal, newVal) { 33 | // console.log('componentDidUpdate', propName, oldVal, newVal) 34 | } 35 | 36 | componentUpdated() { 37 | // console.log('componentUpdated', this.showAuthorName, store.author) 38 | } 39 | 40 | btn = createRef(null) 41 | 42 | render() { 43 | return ( 44 |
45 |

The Art of War - {store.author}

46 | { 47 | store.author === 'Sun Tzu' ? 48 | : '' 49 | } 50 |
51 | ); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /packages/router/LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2021 Google LLC. All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | 3. Neither the name of the copyright holder nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /packages/router/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "quark-router", 3 | "version": "1.0.0", 4 | "description": "A router for Quark", 5 | "type": "module", 6 | "main": "./lib/index.js", 7 | "module": "./lib/index.js", 8 | "types": "./lib/index.d.ts", 9 | "exports": { 10 | ".": { 11 | "import": "./lib/index.js", 12 | "types": "./lib/index.d.ts" 13 | }, 14 | "./router": { 15 | "import": "./lib/router.js", 16 | "types": "./lib/router.d.ts" 17 | }, 18 | "./routes": { 19 | "import": "./lib/routes.js", 20 | "types": "./lib/routes.d.ts" 21 | }, 22 | "./quark-link": { 23 | "import": "./lib/quark-link.js", 24 | "types": "./lib/quark-link.d.ts" 25 | } 26 | }, 27 | "browserslist": "chrome >= 53, edge >= 79, firefox >= 63, opera >= 40, safari >= 10.1, ChromeAndroid >= 53, FirefoxAndroid >= 63, ios_saf >= 10.3, Samsung >= 6.0", 28 | "files": [ 29 | "lib/*", 30 | "README.md", 31 | "package.json" 32 | ], 33 | "scripts": { 34 | "build": "vite build --config vite.config.build.ts && vite build --config vite.config.build-umd.ts" 35 | }, 36 | "dependencies": { 37 | "@babel/runtime": "^7.23.8", 38 | "tslib": "^2.6.2", 39 | "urlpattern-polyfill": "^9.0.0" 40 | }, 41 | "peerDependencies": { 42 | "quarkc": "^1.0.41 || ^2.0.0" 43 | }, 44 | "devDependencies": { 45 | "@babel/core": "^7.22.5", 46 | "@babel/plugin-proposal-decorators": "^7.22.5", 47 | "@babel/plugin-transform-runtime": "^7.22.5", 48 | "@babel/preset-env": "^7.22.5", 49 | "@types/node": "^14.18.52", 50 | "typescript": "^5.4.2", 51 | "vite": "^4.3.9", 52 | "vite-plugin-dts": "^3.8.1" 53 | }, 54 | "keywords": [ 55 | "router", 56 | "quarkc", 57 | "web-components" 58 | ], 59 | "author": "nieyt", 60 | "license": "ISC" 61 | } 62 | -------------------------------------------------------------------------------- /packages/core/test/components/test-watch.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | QuarkElement, 3 | customElement, 4 | state, 5 | property, 6 | watch, 7 | computed, 8 | } from "../../src/main"; 9 | import { spy, SinonSpy } from 'sinon'; 10 | 11 | 12 | const tag = 'test-watch'; 13 | export const ImmediateWatcherSpies = new WeakMap< 14 | TestWatch, 15 | SinonSpy, ReturnType> 16 | >(); 17 | export const ComputedWatcherSpies = new WeakMap(); 18 | 19 | declare global { 20 | interface HTMLElementTagNameMap { 21 | [tag]: TestWatch; 22 | } 23 | } 24 | 25 | @customElement({ tag }) 26 | class TestWatch extends QuarkElement { 27 | constructor() { 28 | super(); 29 | const watcherSpy = spy(this as TestWatch, 'immediateStateWatcher'); 30 | ImmediateWatcherSpies.set(this, watcherSpy); 31 | ComputedWatcherSpies.set(this, spy()); 32 | } 33 | 34 | componentWillUnmount() { 35 | ImmediateWatcherSpies.delete(this); 36 | ComputedWatcherSpies.delete(this); 37 | } 38 | 39 | @state() 40 | state = 0; 41 | 42 | @property({ type: Number }) 43 | prop = 0; 44 | 45 | @watch('state') 46 | stateWatcher(newVal: number, oldVal: number) {} 47 | 48 | @watch('state', { immediate: true }) 49 | immediateStateWatcher(newVal: number, oldVal: number) { 50 | ImmediateWatcherSpies.get(this)?.(newVal, oldVal); 51 | } 52 | 53 | @watch('prop') 54 | propWatcher(newVal: number, oldVal: number) {} 55 | 56 | @computed() 57 | get sum() { 58 | ComputedWatcherSpies.get(this)?.(); 59 | return this.state + this.prop; 60 | } 61 | 62 | render() { 63 | return ( 64 |
65 |
{this.state}
66 |
{this.prop}
67 |
{this.sum}
68 |
69 | ); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /packages/core/src/core/component.js: -------------------------------------------------------------------------------- 1 | import { Fragment } from './create-element'; 2 | 3 | /** 4 | * Base Component class. 5 | * @param {object} props The initial component props 6 | */ 7 | export function Component(props) { 8 | this.props = props; 9 | this._dirty = false; 10 | } 11 | 12 | /** 13 | * Accepts `props`, and returns a new Virtual DOM tree to build. 14 | * Virtual DOM is generally constructed via [JSX](http://jasonformat.com/wtf-is-jsx). 15 | * @param {object} props Props (eg: JSX attributes) received from parent 16 | * element/component 17 | * @returns {import('./index').ComponentChildren | void} 18 | */ 19 | Component.prototype.render = Fragment; 20 | 21 | /** 22 | * @param vnode 23 | * @param {number | null} [childIndex] 24 | */ 25 | export function getDomSibling(vnode, childIndex) { 26 | if (childIndex == null) { 27 | // Use childIndex==null as a signal to resume the search from the vnode's sibling 28 | return vnode._parent 29 | ? getDomSibling(vnode._parent, vnode._parent._children.indexOf(vnode) + 1) 30 | : null; 31 | } 32 | 33 | let sibling; 34 | for (; childIndex < vnode._children.length; childIndex++) { 35 | sibling = vnode._children[childIndex]; 36 | 37 | if (sibling != null && sibling._dom != null) { 38 | // Since updateParentDomPointers keeps _dom pointer correct, 39 | // we can rely on _dom to tell us if this subtree contains a 40 | // rendered DOM node, and what the first rendered DOM node is 41 | return sibling._dom; 42 | } 43 | } 44 | 45 | // If we get here, we have not found a DOM node in this vnode's children. 46 | // We must resume from this vnode's sibling (in it's parent _children array) 47 | // Only climb up and search the parent if we aren't searching through a DOM 48 | // VNode (meaning we reached the DOM parent of the original vnode that began 49 | // the search) 50 | return typeof vnode.type == 'function' ? getDomSibling(vnode) : null; 51 | } 52 | -------------------------------------------------------------------------------- /demo/src/pages/home.tsx: -------------------------------------------------------------------------------- 1 | import { QuarkElement, computed, customElement, internalProp, property, state, watch } from "quarkc"; 2 | import style from "./style.less?inline"; 3 | 4 | declare global { 5 | interface HTMLElementTagNameMap { 6 | "home-component": MyComponent; 7 | } 8 | } 9 | 10 | @customElement({ tag: "home-component", style }) 11 | class MyComponent extends QuarkElement { 12 | @property({ type: String }) 13 | text = "hello world" 14 | 15 | @property({ type: Number }) 16 | count = 0 17 | 18 | @internalProp() 19 | welcomes: string[] = []; 20 | 21 | @state() 22 | loggerRunCount = 0 23 | 24 | @state() 25 | counter = 0; 26 | 27 | private _counterTimer = 0; 28 | 29 | initCounter() { 30 | const runCounter = () => { 31 | this._counterTimer = window.setTimeout(() => { 32 | this.counter++; 33 | runCounter(); 34 | }, 1000) 35 | }; 36 | runCounter(); 37 | } 38 | 39 | componentDidMount() { 40 | // console.log("home dom loaded!") 41 | this.initCounter(); 42 | } 43 | 44 | componentWillUnmount() { 45 | // console.log('home dom will unmount'); 46 | window.clearTimeout(this._counterTimer); 47 | } 48 | 49 | @watch('count', { immediate: true }) 50 | countLogger(newVal, oldVal) { 51 | // console.log('home countLogger', newVal, oldVal); 52 | this.loggerRunCount++; 53 | } 54 | 55 | @computed() 56 | get counterGreet() { 57 | // console.log('home @computed counterGreet') 58 | return `${this.text} ${this.counter} times`; 59 | } 60 | 61 | componentDidUpdate(propName, oldVal, newVal) { 62 | console.log("home dom updated!", propName, oldVal, newVal) 63 | } 64 | 65 | render() { 66 | return ( 67 |
68 | home {this.welcomes.join('')} 69 |
passed down count: {this.count} 70 |
watcher run count: {this.loggerRunCount} 71 |
{this.counterGreet} 72 |
73 | ); 74 | } 75 | } 76 | 77 | export default MyComponent; 78 | -------------------------------------------------------------------------------- /demo/src/pages/child.tsx: -------------------------------------------------------------------------------- 1 | import { QuarkElement, property, customElement } from "quarkc" 2 | import { Routes, RouteEvent, RouteMethodEnum } from "quark-router" 3 | import "./child-second" 4 | import style from "./style.less?inline" 5 | 6 | declare global { 7 | interface HTMLElementTagNameMap { 8 | "child-component": ChildComponent; 9 | "child-first": Child1; 10 | } 11 | } 12 | 13 | @customElement({ tag: "child-first", style }) 14 | class Child1 extends QuarkElement { 15 | @property({ type: String }) 16 | text = "hello world" 17 | 18 | componentDidMount() { 19 | // console.log('dom loaded!', 'child') 20 | } 21 | 22 | goToLink() { 23 | // this.$emit('quark-route-push', { detail: { path: '1122', query: { a: 1, b: 2 } }, composed: true, }) 24 | this.dispatchEvent( 25 | new RouteEvent(RouteMethodEnum.push, { 26 | path: '2/1', 27 | query: { 28 | word1: 'hello', 29 | word2: 'quark', 30 | }, 31 | callback() { 32 | // console.log('enter function excuted') 33 | } 34 | }) 35 | ); 36 | } 37 | 38 | render() { 39 | return ( 40 | <> 41 |

child1 content

42 | 43 | 44 | ); 45 | } 46 | } 47 | 48 | @customElement({ tag: "child-component", style }) 49 | class ChildComponent extends QuarkElement { 50 | private _routes = new Routes(this, [ 51 | {path: '1', render: () => }, 52 | {path: '2/*', render: () => }, 53 | ]) 54 | @property({ type: String }) 55 | text = "hello world" 56 | 57 | render() { 58 | return ( 59 |
60 |
    61 |
  • child/1
  • 62 |
  • child/2/1
  • 63 |
64 |
65 | { this._routes.outlet() } 66 |
67 |
68 | ); 69 | } 70 | } 71 | 72 | export { 73 | ChildComponent 74 | }; 75 | -------------------------------------------------------------------------------- /packages/core/test/components/test-property.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | QuarkElement, 3 | customElement, 4 | property, 5 | } from "../../src/main" 6 | 7 | const tag = 'test-property'; 8 | 9 | declare global { 10 | interface HTMLElementTagNameMap { 11 | [tag]: TestProperty; 12 | } 13 | } 14 | 15 | @customElement({ tag }) 16 | class TestProperty extends QuarkElement { 17 | @property() 18 | testattr = 'lowercased'; 19 | 20 | /** except for initial value, will work the same as testattr */ 21 | @property() 22 | testAttr = 'camelcased'; 23 | 24 | @property({ attribute: 'test-attr2' }) 25 | testAttr2 = '0'; 26 | 27 | @property({ type: Number }) 28 | testattr3 = 0; 29 | 30 | @property({ 31 | converter: (val) => { 32 | return val ? parseInt(val, 2) : 0; 33 | }, 34 | }) 35 | testattr4 = 0; 36 | 37 | @property({ observed: false }) 38 | testattr5 = '0'; 39 | 40 | @property({ internal: true }) 41 | testattr6: string[] = [] 42 | 43 | /** boolean property with its value default to true should be ignored */ 44 | @property({ type: Boolean }) 45 | testattr7 = true; 46 | 47 | @property({ type: Number }) 48 | testattr8 = 18; 49 | 50 | /** boolean property with its value default to true should be ignored */ 51 | @property({ type: Boolean, attribute: 'aria-hidden' }) 52 | testAriaHidden = false; 53 | 54 | render() { 55 | return ( 56 |
57 |
{this.testattr}
58 |
{this.testAttr}
59 |
{this.testAttr2}
60 |
{typeof this.testattr3}{this.testattr3}
61 |
{this.testattr4}
62 |
{this.testattr5}
63 |
{this.testattr6.join(' ')}
64 |
{this.testattr7.toString()}
65 |
{this.testAriaHidden.toString()}
66 |
{this.testattr8.toString()}
67 |
68 | ); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /packages/create-quarkc/template-quarkc-app/src/components/Header/index.css: -------------------------------------------------------------------------------- 1 | .header { 2 | height: 72px; 3 | z-index: 30; 4 | top: 0px; 5 | position: relative; 6 | min-width: 860px; 7 | } 8 | .sticky { 9 | position: sticky; 10 | } 11 | .header>div { 12 | margin-left: auto; 13 | margin-right: auto; 14 | } 15 | .container { 16 | margin: 0 auto; 17 | padding: 1.25rem; 18 | display: flex; 19 | justify-content: space-between; 20 | align-items: center; 21 | border-bottom: 1px solid var(--line-light); 22 | background-color: var(--main-bg); 23 | } 24 | .toper-bar, 25 | .left-bar { 26 | display: flex; 27 | align-items: center; 28 | } 29 | .left-bar a { 30 | color: rgb(107 114 128); 31 | display: flex; 32 | align-items: center; 33 | margin-right: 0.5rem; 34 | } 35 | .header .left-bar a img { 36 | height: 2rem; 37 | } 38 | .left-bar span { 39 | display: flex; 40 | margin-right: 2rem; 41 | align-items: center; 42 | color: rgb(156 163 175); 43 | } 44 | .menu-group { 45 | display: flex; 46 | gap: 2rem; 47 | } 48 | .nav-item a { 49 | font-size: 14px; 50 | color: var(--text-font-color-light); 51 | font-family: var(--font-family-base); 52 | text-decoration: none; 53 | } 54 | .nav-item a:hover { 55 | color: var(--brand-color); 56 | } 57 | .translations, 58 | .appearance, 59 | .social-links { 60 | display: flex; 61 | } 62 | .translations:before, 63 | .appearance:before, 64 | .social-links:before{ 65 | margin: 0 16px; 66 | width: 1px; 67 | height: 24px; 68 | background-color: rgba(60, 60, 67, 0.12); 69 | content: ""; 70 | } 71 | .social-links, 72 | .translate-lang { 73 | display: flex; 74 | align-items: center; 75 | } 76 | 77 | svg { 78 | width: 20px; 79 | height: 20px; 80 | fill: currentColor; 81 | } 82 | 83 | .social-links a, .translate-lang a { 84 | width: 40px; 85 | height: 36px; 86 | display: flex; 87 | align-items: center; 88 | justify-content: center; 89 | color: #3c3c43b3; 90 | } 91 | 92 | .social-links a:hover svg, .translate-lang a:hover svg { 93 | color: #3c3c43eb; 94 | transition: color .25s; 95 | } -------------------------------------------------------------------------------- /packages/create-quarkc/template-quarkc-app-ts/src/components/Header/index.css: -------------------------------------------------------------------------------- 1 | .header { 2 | height: 72px; 3 | z-index: 30; 4 | top: 0px; 5 | position: relative; 6 | min-width: 860px; 7 | } 8 | .sticky { 9 | position: sticky; 10 | } 11 | .header>div { 12 | margin-left: auto; 13 | margin-right: auto; 14 | } 15 | .container { 16 | margin: 0 auto; 17 | padding: 1.25rem; 18 | display: flex; 19 | justify-content: space-between; 20 | align-items: center; 21 | border-bottom: 1px solid var(--line-light); 22 | background-color: var(--main-bg); 23 | } 24 | .toper-bar, 25 | .left-bar { 26 | display: flex; 27 | align-items: center; 28 | } 29 | .left-bar a { 30 | color: rgb(107 114 128); 31 | display: flex; 32 | align-items: center; 33 | margin-right: 0.5rem; 34 | } 35 | .header .left-bar a img { 36 | height: 2rem; 37 | } 38 | .left-bar span { 39 | display: flex; 40 | margin-right: 2rem; 41 | align-items: center; 42 | color: rgb(156 163 175); 43 | } 44 | .menu-group { 45 | display: flex; 46 | gap: 2rem; 47 | } 48 | .nav-item a { 49 | font-size: 14px; 50 | color: var(--text-font-color-light); 51 | font-family: var(--font-family-base); 52 | text-decoration: none; 53 | } 54 | .nav-item a:hover { 55 | color: var(--brand-color); 56 | } 57 | .translations, 58 | .appearance, 59 | .social-links { 60 | display: flex; 61 | } 62 | .translations:before, 63 | .appearance:before, 64 | .social-links:before{ 65 | margin: 0 16px; 66 | width: 1px; 67 | height: 24px; 68 | background-color: rgba(60, 60, 67, 0.12); 69 | content: ""; 70 | } 71 | .social-links, 72 | .translate-lang { 73 | display: flex; 74 | align-items: center; 75 | } 76 | 77 | svg { 78 | width: 20px; 79 | height: 20px; 80 | fill: currentColor; 81 | } 82 | 83 | .social-links a, .translate-lang a { 84 | width: 40px; 85 | height: 36px; 86 | display: flex; 87 | align-items: center; 88 | justify-content: center; 89 | color: #3c3c43b3; 90 | } 91 | 92 | .social-links a:hover svg, .translate-lang a:hover svg { 93 | color: #3c3c43eb; 94 | transition: color .25s; 95 | } -------------------------------------------------------------------------------- /packages/router/src/quark-link.tsx: -------------------------------------------------------------------------------- 1 | import { QuarkElement, property, state, customElement } from '../../../packages/core' 2 | import type { Routes } from './routes'; 3 | import type { Router } from './router'; 4 | import { RouterModeEnum } from './router'; 5 | import { RouterJumpEvent, RouterJumpMethodEnum, } from './routes'; 6 | import { eventBus } from './eventEmitter'; 7 | import style from './style.css?inline'; 8 | 9 | declare global { 10 | interface HTMLElementTagNameMap { 11 | 'quark-link': QuarkLink; 12 | } 13 | } 14 | 15 | @customElement({ tag: 'quark-link', style }) 16 | class QuarkLink extends QuarkElement { 17 | @property({ type: String }) 18 | to = '' 19 | 20 | @property({ 21 | type: Boolean, 22 | }) 23 | replace = false; 24 | 25 | @state() 26 | path = ''; 27 | 28 | @state() 29 | prefix = ''; 30 | 31 | @state() 32 | mode = RouterModeEnum.history; 33 | 34 | componentDidMount() { 35 | if (!this.to) return; 36 | /** 37 | * TODO 是否需要eventBus?a标签是否需要配置生成的路径 38 | * @param routes 路由对象 39 | */ 40 | const listener = (routes: Routes) => { 41 | if (routes?.host?.shadowRoot?.contains(this)) { 42 | /* 如果接收到发送事件的距离最近的父/祖先级节点,则截获routes对象,获取link方法生成的path路径 43 | 并取消监听,避免再接受到其他无关事件 */ 44 | this.path = this.path || routes.link(this.to); 45 | eventBus.off('routes-host-mounted', listener); 46 | } 47 | }; 48 | eventBus.on('routes-host-mounted', listener); 49 | eventBus.once('router-host-mounted', (router: Router) => { 50 | this.mode = router.mode; 51 | if (this.mode === RouterModeEnum.hash) { 52 | this.prefix = '#'; 53 | } 54 | }); 55 | eventBus.emit('link-mounted', null); 56 | } 57 | 58 | handleLinkClick(e) { 59 | e.preventDefault(); 60 | const type = this.replace ? RouterJumpMethodEnum.replace : RouterJumpMethodEnum.push; 61 | const fullPath = this.path || this.to || ''; 62 | this.dispatchEvent(new RouterJumpEvent(type, { 63 | path: fullPath.split('?')[0], 64 | fullPath, 65 | })); 66 | } 67 | 68 | // TODO 可配置link生成的标签 69 | render() { 70 | return ( 71 | this.handleLinkClick(e)} part="root"> 72 | 73 | 74 | ); 75 | } 76 | } 77 | 78 | export default QuarkLink; -------------------------------------------------------------------------------- /packages/core/src/reactiveController.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2021 Google LLC 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | /** 8 | * An object that can host Reactive Controllers and call their lifecycle 9 | * callbacks. 10 | */ 11 | export interface ReactiveControllerHost { 12 | /** 13 | * Adds a controller to the host, which sets up the controller's lifecycle 14 | * methods to be called with the host's lifecycle. 15 | */ 16 | addController(controller: ReactiveController): void; 17 | 18 | /** 19 | * Removes a controller from the host. 20 | */ 21 | removeController(controller: ReactiveController): void; 22 | 23 | /** 24 | * Requests a host update which is processed asynchronously. The update can 25 | * be waited on via the `updateComplete` property. 26 | */ 27 | requestUpdate(): void; 28 | } 29 | 30 | /** 31 | * A Reactive Controller is an object that enables sub-component code 32 | * organization and reuse by aggregating the state, behavior, and lifecycle 33 | * hooks related to a single feature. 34 | * 35 | * Controllers are added to a host component, or other object that implements 36 | * the `ReactiveControllerHost` interface, via the `addController()` method. 37 | * They can hook their host components's lifecycle by implementing one or more 38 | * of the lifecycle callbacks, or initiate an update of the host component by 39 | * calling `requestUpdate()` on the host. 40 | */ 41 | export interface ReactiveController { 42 | /** 43 | * Called when the host is connected to the component tree. For custom 44 | * element hosts, this corresponds to the `connectedCallback()` lifecycle, 45 | * which is only called when the component is connected to the document. 46 | */ 47 | hostConnected?(): void; 48 | 49 | /** 50 | * Called when the host is disconnected from the component tree. For custom 51 | * element hosts, this corresponds to the `disconnectedCallback()` lifecycle, 52 | * which is called the host or an ancestor component is disconnected from the 53 | * document. 54 | */ 55 | hostDisconnected?(): void; 56 | 57 | /** 58 | * Called after a host update, just before the host calls firstUpdated and 59 | * updated. It is not called in server-side rendering. 60 | * 61 | */ 62 | hostUpdated?(): void; 63 | 64 | /** 65 | * Called when the host is mounted, just after the host calls first render. 66 | */ 67 | hostMounted?():void; 68 | } 69 | -------------------------------------------------------------------------------- /examples/using-local-bundle/src/lib/index.umd.js: -------------------------------------------------------------------------------- 1 | (function(e,o){typeof exports=="object"&&typeof module<"u"?module.exports=o(require("quarkc")):typeof define=="function"&&define.amd?define(["quarkc"],o):(e=typeof globalThis<"u"?globalThis:e||self,e.MyComponent=o(e.Quarkc))})(this,function(e){"use strict";var c=Object.defineProperty;var m=(e,o,l)=>o in e?c(e,o,{enumerable:!0,configurable:!0,writable:!0,value:l}):e[o]=l;var p=(e,o,l)=>(m(e,typeof o!="symbol"?o+"":o,l),l);function o(s,t,i,n){var d=arguments.length,r=d<3?t:n===null?n=Object.getOwnPropertyDescriptor(t,i):n,u;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")r=Reflect.decorate(s,t,i,n);else for(var a=s.length-1;a>=0;a--)(u=s[a])&&(r=(d<3?u(r):d>3?u(t,i,r):u(t,i))||r);return d>3&&r&&Object.defineProperty(t,i,r),r}typeof SuppressedError=="function"&&SuppressedError;const l=`:root{font-family:Inter,Avenir,Helvetica,Arial,sans-serif;font-size:16px;font-weight:400;color-scheme:light dark;color:#ffffffde;background-color:#242424}:host{display:block;margin:0 auto;padding:2rem;text-align:center;border:2px solid #0da6e9;width:500px}.logo{height:6em;padding:1.5em;will-change:filter}.logo:hover{filter:drop-shadow(0 0 2em #0da6e9aa);transition:all .5s}.card{padding:2em}button{border-radius:8px;border:1px solid transparent;padding:.6em 1.2em;font-size:1em;font-weight:500;font-family:inherit;cursor:pointer;transition:border-color .25s}button:hover{border-color:#0da6e9}button:focus,button:focus-visible{outline:4px auto -webkit-focus-ring-color}.read-the-docs{color:#888}@media (prefers-color-scheme: light){:root{color:#213547;background-color:#fff}a:hover{color:#747bff}button{background-color:#f9f9f9}} 2 | `;let f=class extends e.QuarkElement{constructor(){super(...arguments);p(this,"count",0);p(this,"text","");p(this,"add",()=>{this.count+=1})}componentDidMount(){console.log("dom loaded!")}render(){return e.QuarkElement.h(e.QuarkElement.Fragment,null,e.QuarkElement.h("div",null,e.QuarkElement.h("a",{href:"https://quarkc.hellobike.com",target:"_blank"},e.QuarkElement.h("img",{src:"https://fastly.jsdelivr.net/npm/quark-static@latest/quark-logo.png",class:"logo",alt:"quark logo"}))),e.QuarkElement.h("h1",null,"Quark - ",this.text),e.QuarkElement.h("div",{className:"card"},e.QuarkElement.h("button",{onClick:this.add},"count is: ",this.count)))}};return o([e.property({type:Number})],f.prototype,"count",void 0),o([e.property({type:String})],f.prototype,"text",void 0),f=o([e.customElement({tag:"my-component",style:l})],f),f}); 3 | -------------------------------------------------------------------------------- /packages/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "quarkc", 3 | "version": "2.2.1", 4 | "description": "A Web Components framework", 5 | "type": "module", 6 | "module": "./lib/index.js", 7 | "main": "./lib/index.umd.js", 8 | "browser": "./lib/index.browser.js", 9 | "types": "./lib/src/main.d.ts", 10 | "exports": { 11 | ".": { 12 | "browser": "./lib/index.browser.js", 13 | "types": "./lib/src/main.d.ts", 14 | "default": "./lib/index.js" 15 | } 16 | }, 17 | "browserslist": "chrome >= 53, edge >= 79, firefox >= 63, opera >= 40, safari >= 10.1, ChromeAndroid >= 53, FirefoxAndroid >= 63, ios_saf >= 10.3, Samsung >= 6.0", 18 | "files": [ 19 | "lib/*", 20 | "README.md", 21 | "package.json" 22 | ], 23 | "scripts": { 24 | "bundle": "rollup -c --configPlugin @rollup/plugin-typescript", 25 | "dev": "pnpm run bundle --watch", 26 | "build": "rimraf lib && pnpm run bundle", 27 | "test": "web-test-runner test/**/*.test.ts --node-resolve --puppeteer", 28 | "test:watch": "pnpm run test --watch" 29 | }, 30 | "dependencies": { 31 | "@babel/runtime": "^7.22.5" 32 | }, 33 | "devDependencies": { 34 | "@babel/core": "^7.22.5", 35 | "@babel/plugin-proposal-decorators": "^7.22.5", 36 | "@babel/plugin-transform-runtime": "^7.22.5", 37 | "@babel/preset-env": "^7.22.5", 38 | "@open-wc/testing": "^4.0.0", 39 | "@rollup/plugin-babel": "^6.0.4", 40 | "@rollup/plugin-commonjs": "^25.0.7", 41 | "@rollup/plugin-json": "^6.1.0", 42 | "@rollup/plugin-node-resolve": "^15.2.3", 43 | "@rollup/plugin-replace": "^5.0.5", 44 | "@rollup/plugin-terser": "^0.4.4", 45 | "@rollup/plugin-typescript": "^11.1.6", 46 | "@types/mocha": "^10.0.6", 47 | "@types/node": "^14.18.52", 48 | "@web/dev-server-esbuild": "^1.0.2", 49 | "@web/test-runner": "^0.18.1", 50 | "@web/test-runner-puppeteer": "^0.16.0", 51 | "mocha": "^10.4.0", 52 | "rimraf": "3.0.2", 53 | "rollup": "^4.17.0", 54 | "rollup-plugin-filesize": "^10.0.0", 55 | "sinon": "^17.0.1", 56 | "typescript": "^5.4.5" 57 | }, 58 | "homepage": "https://quarkc.hellobike.com", 59 | "repository": { 60 | "type": "git", 61 | "url": "git@github.com:hellof2e/quark.git" 62 | }, 63 | "keywords": [ 64 | "quarkc", 65 | "web components", 66 | "shadow dom", 67 | "custom element" 68 | ], 69 | "publishConfig": { 70 | "access": "public", 71 | "registry": "https://registry.npmjs.org/" 72 | }, 73 | "author": "", 74 | "license": "MIT" 75 | } 76 | -------------------------------------------------------------------------------- /packages/core/test/render.test.ts: -------------------------------------------------------------------------------- 1 | import { fixture, expect } from '@open-wc/testing'; 2 | import { createElement, Fragment } from '../src/core/create-element'; 3 | import { render } from '../src/core/render'; 4 | 5 | const WELCOME = 'hello, world!'; 6 | const ROOT_HTML = '
'; 7 | 8 | const renderIntoRoot = async (vnode) => { 9 | const root = await fixture(ROOT_HTML); 10 | render(vnode, root); 11 | return root; 12 | }; 13 | 14 | describe('render', () => { 15 | it('render text into root', async () => { 16 | const root = await renderIntoRoot(WELCOME); 17 | expect(root.textContent).to.equal(WELCOME); 18 | expect(root.outerHTML).to.equal(`
${WELCOME}
`); 19 | }); 20 | 21 | it('render node into root', async () => { 22 | const node = createElement('div', {}, [WELCOME]); 23 | const root = await renderIntoRoot(node); 24 | const child = root.firstChild; 25 | expect(child).to.exist; 26 | expect(child?.textContent).to.equal(WELCOME); 27 | const HTML = `
${WELCOME}
`; 28 | expect(root.innerHTML).to.equal(HTML); 29 | expect(root.outerHTML).to.equal(`
${HTML}
`); 30 | }); 31 | 32 | it('attribute', async () => { 33 | const node = createElement('div', { title: '123' }); 34 | const root = await renderIntoRoot(node); 35 | expect(root.firstChild).to.be.instanceOf(HTMLDivElement); 36 | expect((root.firstChild as HTMLDivElement).getAttribute('title')).to.equal('123'); 37 | }); 38 | 39 | it('nested and reused nodes', async () => { 40 | const para = createElement('p', { className: 'para' }, ['lorem']); 41 | const node = createElement('div', {}, [ 42 | para, 43 | WELCOME, 44 | para, 45 | ]); 46 | const root = await renderIntoRoot(node); 47 | const child = root.firstChild; 48 | expect(child).to.be.instanceOf(HTMLDivElement); 49 | expect(child?.childNodes.length).to.equal(3); 50 | const paraElems = (child as HTMLDivElement).querySelectorAll('.para'); 51 | expect(paraElems).to.exist; 52 | expect(paraElems?.length).to.equal(2); 53 | expect(paraElems?.[1].textContent).to.equal('lorem'); 54 | }); 55 | 56 | it('render fragment into root', async () => { 57 | const node = createElement(Fragment, {}, [ 58 | createElement('div', {}, [WELCOME]), 59 | createElement('div', {}, [WELCOME]), 60 | ]) 61 | const root = await renderIntoRoot(node); 62 | expect(root.firstElementChild).to.exist; 63 | expect(root.childNodes.length).to.equal(2); 64 | expect(root.firstElementChild!.textContent).to.equal(WELCOME); 65 | }); 66 | 67 | it('render null into root', async () => { 68 | const root = await renderIntoRoot(null); 69 | expect(root.outerHTML).to.equal(ROOT_HTML); 70 | }); 71 | }); 72 | 73 | -------------------------------------------------------------------------------- /packages/create-quarkc/COMPONENT.dev.md: -------------------------------------------------------------------------------- 1 | # 构建跨技术栈/原生组件 2 | 3 | 创建跨技术栈/原生Web组件 4 | 5 | 6 | ## 安装并创建 7 | 8 | 1. 🔨工程安装 9 | ```js 10 | npm create quarkc@latest 11 | // 选择 component... 12 | npm start 13 | ``` 14 | 15 | 2. ✍️ 创建组件 16 | 17 | project-name/src/index.tsx: 18 | 19 | ```jsx 20 | import { QuarkElement, property, customElement } from "quarkc" 21 | import style from "./main.css" 22 | 23 | @customElement({ tag: "my-element", style }) // 自定义标签/组件、CSS 24 | export default class MyElement extends QuarkElement { 25 | @property({ type: Number }) // 外部属性 26 | count = 0; 27 | 28 | add = () => { 29 | this.count += 1; 30 | } 31 | 32 | render() { 33 | return ( 34 | 35 | ); 36 | } 37 | } 38 | ``` 39 | 40 | 41 | 3. 构建 42 | ``` 43 | npm run build 44 | ``` 45 | 46 | 47 | 4. 使用 48 | 49 | 产物可以直接被任何前端项目使用 50 | 51 | ```js 52 | import "path/your-component" 53 | ``` 54 | 55 | 或发布到 npm: 56 | ```js 57 | import "your-component" 58 | ``` 59 | 60 | 或使用 CDN: 61 | ```js 62 | 63 | 64 | ``` 65 | 66 | ```html 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | ``` 82 | 83 | ## 文档 84 | 85 | 完整文档,请访问 [quarkc.hellobike.com](https://quarkc.hellobike.com) 86 | 87 | 88 | ## 特性 89 | 90 | - 跨技术栈:组件可以在任何框架或无框架的环境下使用,让你的代码更具复用性 91 | - 组件体积极小,性能极高:因为 Quarkc 使用的是浏览器原生 API,所以你的组件可以达到最优性能,且体积小巧 92 | - Web Components, Simple, Fast! 93 | - 浏览器原生API,组件可以跨技术栈使用 94 | - 没有前端框架 Runtime,Web 组件体积小到极致 95 | - 高性能设计,Shadow DOM 与 Virtual DOM 融合 96 | - 组件直接解耦,独立打磨,按需引用 97 | 98 | 99 | ## 优秀案例 100 | 101 | | 作者 | github 地址 | 截图 / 链接 102 | | ---- | ---- | ----- | 103 | | @xsf0105 | https://github.com/xsf0105/dark-light-element | https://unpkg.com/dark-light-element@latest/demo.html | 104 | | @hellof2e | https://github.com/hellof2e/quark-doc-header | ![1685501041275](https://github.com/hellof2e/quark/assets/14307551/24dd5626-e6a9-452c-9c95-c2cdb8891573) https://quarkc.hellobike.com/#/ | 105 | | @dyf19118 | https://github.com/dyf19118/quark-ui-rate | ![image](https://github.com/hellof2e/quark-cli/assets/14307551/e11e6c49-4c18-4bca-adc3-01a7198ab2e2) | 106 | | @xsf0105 | https://github.com/hellof2e/quark-doc-home | ![1686575964690](https://github.com/hellof2e/quark/assets/14307551/9618427c-916b-4dfd-b28b-0e8e0f6ce744) | 107 | 108 | -------------------------------------------------------------------------------- /packages/core/rollup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "rollup"; 2 | import commonjs from "@rollup/plugin-commonjs"; 3 | import { nodeResolve } from "@rollup/plugin-node-resolve"; 4 | import { babel } from "@rollup/plugin-babel"; 5 | import typescript from "@rollup/plugin-typescript"; 6 | import terser from "@rollup/plugin-terser"; 7 | import json from "@rollup/plugin-json" 8 | import filesize from 'rollup-plugin-filesize'; 9 | import replace from '@rollup/plugin-replace'; 10 | 11 | const extensions = [".js", ".ts", ".tsx"]; 12 | const commonPlugins = [ 13 | typescript({ exclude: ['test/**', 'rollup.config.ts'] }), 14 | json(), 15 | commonjs(), 16 | nodeResolve({ 17 | extensions, 18 | }), 19 | babel({ 20 | babelHelpers: "runtime", 21 | exclude: "node_modules/**", 22 | extensions, 23 | }), 24 | filesize(), 25 | ]; 26 | const getPlugins = ({ 27 | isProd, 28 | isBrowserOnly, 29 | }: { 30 | /** 是否为生产包 */ 31 | isProd?: boolean; 32 | /** 是否仅能用在浏览器环境中 */ 33 | isBrowserOnly?: boolean; 34 | } = {}) => { 35 | const plugins = commonPlugins.slice(); 36 | 37 | if (isBrowserOnly) { 38 | plugins.push(replace({ 39 | preventAssignment: true, 40 | 'process.env.NODE_ENV': `'${isProd ? 'production': 'development'}'`, 41 | })); 42 | } 43 | 44 | if (isProd) { 45 | return [ 46 | ...plugins, 47 | terser(), 48 | ]; 49 | } 50 | 51 | return plugins; 52 | }; 53 | const input = "./src/main.ts"; 54 | const dir = "lib"; 55 | export default defineConfig([ 56 | { 57 | input, 58 | output: [ 59 | { 60 | dir, 61 | entryFileNames: 'index.js', 62 | format: "es", 63 | }, 64 | ], 65 | plugins: getPlugins(), 66 | external: /@babel\/runtime/, 67 | }, 68 | { 69 | input, 70 | output: [ 71 | { 72 | dir, 73 | entryFileNames: 'index.browser.js', 74 | format: "es", 75 | }, 76 | ], 77 | plugins: getPlugins({ isBrowserOnly: true }), 78 | }, 79 | { 80 | input, 81 | output: [ 82 | { 83 | dir, 84 | entryFileNames: 'index.browser.prod.js', 85 | format: "es", 86 | }, 87 | ], 88 | plugins: getPlugins({ isBrowserOnly: true, isProd: true }), 89 | }, 90 | { 91 | input, 92 | output: [ 93 | { 94 | dir, 95 | entryFileNames: 'index.umd.js', 96 | format: 'umd', 97 | name: 'Quarkc', 98 | }, 99 | ], 100 | plugins: getPlugins({ isBrowserOnly: true }), 101 | }, 102 | { 103 | input, 104 | output: [ 105 | { 106 | dir, 107 | entryFileNames: 'index.umd.prod.js', 108 | format: 'umd', 109 | name: 'Quarkc', 110 | }, 111 | ], 112 | plugins: getPlugins({ isBrowserOnly: true, isProd: true }), 113 | }, 114 | ]); 115 | -------------------------------------------------------------------------------- /examples/using-local-bundle/src/lib/index.js: -------------------------------------------------------------------------------- 1 | var p = Object.defineProperty; 2 | var h = (t, o, e) => o in t ? p(t, o, { enumerable: !0, configurable: !0, writable: !0, value: e }) : t[o] = e; 3 | var s = (t, o, e) => (h(t, typeof o != "symbol" ? o + "" : o, e), e); 4 | import { property as u, customElement as m, QuarkElement as r } from "quarkc"; 5 | function d(t, o, e, i) { 6 | var c = arguments.length, n = c < 3 ? o : i === null ? i = Object.getOwnPropertyDescriptor(o, e) : i, a; 7 | if (typeof Reflect == "object" && typeof Reflect.decorate == "function") 8 | n = Reflect.decorate(t, o, e, i); 9 | else 10 | for (var f = t.length - 1; f >= 0; f--) 11 | (a = t[f]) && (n = (c < 3 ? a(n) : c > 3 ? a(o, e, n) : a(o, e)) || n); 12 | return c > 3 && n && Object.defineProperty(o, e, n), n; 13 | } 14 | const g = `:root{font-family:Inter,Avenir,Helvetica,Arial,sans-serif;font-size:16px;font-weight:400;color-scheme:light dark;color:#ffffffde;background-color:#242424}:host{display:block;margin:0 auto;padding:2rem;text-align:center;border:2px solid #0da6e9;width:500px}.logo{height:6em;padding:1.5em;will-change:filter}.logo:hover{filter:drop-shadow(0 0 2em #0da6e9aa);transition:all .5s}.card{padding:2em}button{border-radius:8px;border:1px solid transparent;padding:.6em 1.2em;font-size:1em;font-weight:500;font-family:inherit;cursor:pointer;transition:border-color .25s}button:hover{border-color:#0da6e9}button:focus,button:focus-visible{outline:4px auto -webkit-focus-ring-color}.read-the-docs{color:#888}@media (prefers-color-scheme: light){:root{color:#213547;background-color:#fff}a:hover{color:#747bff}button{background-color:#f9f9f9}} 15 | `; 16 | let l = class extends r { 17 | constructor() { 18 | super(...arguments); 19 | s(this, "count", 0); 20 | s(this, "text", ""); 21 | s(this, "add", () => { 22 | this.count += 1; 23 | }); 24 | } 25 | componentDidMount() { 26 | // console.log("dom loaded!"); 27 | } 28 | render() { 29 | return r.h( 30 | r.Fragment, 31 | null, 32 | r.h( 33 | "div", 34 | null, 35 | r.h( 36 | "a", 37 | { href: "https://quarkc.hellobike.com", target: "_blank" }, 38 | r.h("img", { src: "https://fastly.jsdelivr.net/npm/quark-static@latest/quark-logo.png", class: "logo", alt: "quark logo" }) 39 | ) 40 | ), 41 | r.h( 42 | "h1", 43 | null, 44 | "Quark - ", 45 | this.text 46 | ), 47 | r.h( 48 | "div", 49 | { className: "card" }, 50 | r.h( 51 | "button", 52 | { onClick: this.add }, 53 | "count is: ", 54 | this.count 55 | ) 56 | ) 57 | ); 58 | } 59 | }; 60 | d([ 61 | u({ type: Number }) 62 | ], l.prototype, "count", void 0); 63 | d([ 64 | u({ type: String }) 65 | ], l.prototype, "text", void 0); 66 | l = d([ 67 | m({ tag: "my-component", style: g }) 68 | ], l); 69 | const k = l; 70 | export { 71 | k as default 72 | }; 73 | -------------------------------------------------------------------------------- /packages/core/src/core/create-element.js: -------------------------------------------------------------------------------- 1 | import { slice } from './util'; 2 | import options from './options'; 3 | 4 | let vnodeId = 0; 5 | 6 | /** 7 | * Create an virtual node (used for JSX) 8 | * @param type The node name or Component 9 | * constructor for this virtual node 10 | * @param {object | null | undefined} props The properties of the virtual node 11 | * @param children The children of the virtual node 12 | * @returns 13 | */ 14 | export function createElement(type, props, children) { 15 | let normalizedProps = {}, 16 | key, 17 | ref, 18 | i; 19 | for (i in props) { 20 | if (i == 'key') key = props[i]; 21 | else if (i == 'ref') ref = props[i]; 22 | else normalizedProps[i] = props[i]; 23 | } 24 | 25 | if (arguments.length > 2) { 26 | normalizedProps.children = 27 | arguments.length > 3 ? slice.call(arguments, 2) : children; 28 | } 29 | 30 | // If a Component VNode, check for and apply defaultProps 31 | // Note: type may be undefined in development, must never error here. 32 | if (typeof type == 'function' && type.defaultProps != null) { 33 | for (i in type.defaultProps) { 34 | if (normalizedProps[i] === undefined) { 35 | normalizedProps[i] = type.defaultProps[i]; 36 | } 37 | } 38 | } 39 | 40 | return createVNode(type, normalizedProps, key, ref, null); 41 | } 42 | 43 | /** 44 | * Create a VNode (used internally by quark) 45 | * @param type The node name or Component 46 | * Constructor for this virtual node 47 | * @param {object | string | number | null} props The properties of this virtual node. 48 | * If this virtual node represents a text node, this is the text of the node (string or number). 49 | * @param {string | number | null} key The key for this virtual node, used when 50 | * diffing it against its children 51 | * @param ref The ref property that will 52 | * receive a reference to its created child 53 | * @returns 54 | */ 55 | export function createVNode(type, props, key, ref, original) { 56 | // V8 seems to be better at detecting type shapes if the object is allocated from the same call site 57 | // Do not inline into createElement and coerceToVNode! 58 | const vnode = { 59 | type, 60 | props, 61 | key, 62 | ref, 63 | _children: null, 64 | _parent: null, 65 | _depth: 0, 66 | _dom: null, 67 | // _nextDom must be initialized to undefined b/c it will eventually 68 | // be set to dom.nextSibling which can return `null` and it is important 69 | // to be able to distinguish between an uninitialized _nextDom and 70 | // a _nextDom that has been set to `null` 71 | _nextDom: undefined, 72 | _component: null, 73 | _hydrating: null, 74 | constructor: undefined, 75 | _original: original == null ? ++vnodeId : original 76 | }; 77 | 78 | // Only invoke the vnode hook if this was *not* a direct copy: 79 | if (original == null && options.vnode != null) options.vnode(vnode); 80 | 81 | return vnode; 82 | } 83 | 84 | export function createRef() { 85 | return { current: null }; 86 | } 87 | 88 | export function Fragment(props) { 89 | return props.children; 90 | } 91 | -------------------------------------------------------------------------------- /.github/workflows/release-package.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | paths: 7 | - "packages/core/package.json" 8 | 9 | jobs: 10 | release: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | with: 15 | fetch-depth: 0 16 | 17 | - name: Setup node 18 | uses: actions/setup-node@v2 19 | with: 20 | node-version: "16" 21 | registry-url: https://registry.npmjs.org/ 22 | 23 | - name: Install pnpm 24 | uses: pnpm/action-setup@v2 25 | with: 26 | version: 8 27 | run_install: false 28 | 29 | # Use cache to reduce installation time 30 | - name: Get pnpm store directory 31 | id: pnpm-cache 32 | shell: bash 33 | run: | 34 | echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT 35 | 36 | - uses: actions/cache@v3 37 | name: Setup pnpm cache 38 | with: 39 | path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} 40 | key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} 41 | restore-keys: | 42 | ${{ runner.os }}-pnpm-store- 43 | 44 | - name: Install dependencies 45 | run: pnpm install 46 | 47 | - name: Build 48 | run: pnpm run build4core 49 | 50 | - name: publish 51 | working-directory: "packages/core" 52 | env: 53 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 54 | run: npm publish 55 | 56 | # 使用 tyankatsu0105/read-package-version-actions@v1 工具来读取对应的package.json 数据 57 | - name: Read package.json 58 | uses: tyankatsu0105/read-package-version-actions@v1 59 | with: 60 | path: "./packages/core" 61 | id: package-version 62 | 63 | # 提交 Tag 64 | - name: create git tag 65 | env: 66 | GITHUB_TOKEN: ${{ secrets.GIT_ACTION }} 67 | run: | 68 | git config --local user.email "${GITHUB_ACTOR}@users.noreply.github.com" 69 | git config --local user.name "${GITHUB_ACTOR}" 70 | git push origin main 71 | git tag v${{ steps.package-version.outputs.version }} 72 | git push origin v${{ steps.package-version.outputs.version }} 73 | 74 | # 下面主要是创建 github 的release 75 | # 关于创建 release 的更多参数,可以查看 actions/create-release@v1 76 | # - name: Create Release for Tag 77 | # id: release_tag 78 | # uses: actions/create-release@v1 79 | # env: 80 | # GITHUB_TOKEN: ${{ secrets.GIT_ACTION }} # 这块需要用到 github的token,因为需要对分之进行代码推送 81 | # with: 82 | # tag_name: v${{ steps.package-version.outputs.version }} 83 | # release_name: Release v${{ steps.package-version.outputs.version }} 84 | # prerelease: false # 是否为预发布版本 85 | # body: | 86 | # 请点击查看 [更新日志](https://github.com/hellof2e/quark-core/releases/tag/v${{ steps.package-version.outputs.version }}). 87 | 88 | - run: npx changelogithub # or changelogithub@0.12 if ensure the stable result 89 | env: 90 | GITHUB_TOKEN: ${{secrets.GIT_ACTION}} 91 | -------------------------------------------------------------------------------- /demo/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { QuarkElement, property, customElement, state, computed } from "quarkc" 2 | import { Router } from "quark-router" 3 | import "./pages/home" 4 | import "./pages/sub" 5 | import "./pages/child" 6 | import style from "./index.less?inline" 7 | 8 | declare global { 9 | interface HTMLElementTagNameMap { 10 | "my-component": MyComponent; 11 | } 12 | } 13 | 14 | @customElement({ tag: "my-component", style }) 15 | class MyComponent extends QuarkElement { 16 | private _routes = new Router(this, [ 17 | {path: '/', render: () => }, 18 | {path: '/sub/:id', render: ({id}) => }, 19 | {path: '/child/*', render: () => }, 20 | {path: '/child', render: () => }, 21 | ], { 22 | mode: 'hash' 23 | }) 24 | 25 | 26 | @property({ type: Number }) // 外部属性 27 | count = 0 // 可以设置默认值 28 | 29 | @property({ type: String }) 30 | text = '' 31 | 32 | @state() 33 | innerCount = 0; 34 | 35 | @state() 36 | welcomes: string[] = []; 37 | 38 | @computed() 39 | get resolvedCount() { 40 | return this.count + this.innerCount; 41 | } 42 | 43 | add = () => { 44 | // 内部事件 45 | this.innerCount += 1 46 | }; 47 | 48 | showWelcome() { 49 | const WELCOME = 'Welcome to Quark!'; 50 | let i = 0; 51 | const timer = window.setInterval(() => { 52 | if (i >= WELCOME.length) { 53 | window.clearInterval(timer); 54 | return; 55 | } 56 | 57 | this.welcomes = [...this.welcomes, WELCOME[i++]]; 58 | }, 100); 59 | } 60 | 61 | shouldComponentUpdate(propName, oldValue, newValue) { 62 | if (propName === 'innerCount') { 63 | return newValue <= 10; 64 | } 65 | 66 | return true; 67 | } 68 | 69 | componentDidUpdate() { 70 | // console.log("parent dom updated!") 71 | } 72 | 73 | componentDidMount() { 74 | // console.log("parent dom loaded!") 75 | this.showWelcome(); 76 | } 77 | 78 | render() { 79 | return ( 80 | <> 81 |
82 |
83 | 84 | 89 | 90 |
91 | 92 |

{this.text} Quarkc

93 | 94 |
95 | 96 |
97 |
98 |
    99 |
  • Home
  • 100 |
  • /sub/3222
  • 101 |
  • /Child/1
  • 102 |
103 |
104 | { this._routes.outlet() } 105 |
106 | 107 | ); 108 | } 109 | } 110 | 111 | export default MyComponent; 112 | -------------------------------------------------------------------------------- /packages/create-quarkc/template-quarkc-app/src/components/Header/index.tsx: -------------------------------------------------------------------------------- 1 | import { QuarkElement, Fragment, property, customElement, state, createRef } from "quarkc"; 2 | import style from "./index.css?inline" 3 | 4 | @customElement({ tag: "app-header", style }) 5 | export default class Header extends QuarkElement { 6 | 7 | @state() 8 | version = '1.0.0' 9 | 10 | @state() 11 | menus = [{ 12 | name: 'home', 13 | link: '/' 14 | },{ 15 | name: 'component', 16 | link: '/docs' 17 | }] 18 | 19 | componentDidMount(): void {} 20 | 21 | render() { 22 | return ( 23 |
24 |
25 |
26 |
27 | 28 | 29 | 30 | 31 | { this.version } 32 | 33 |
34 |
35 | 36 |
37 | 48 | 59 |
60 |
61 |
62 |
63 | ); 64 | } 65 | } -------------------------------------------------------------------------------- /packages/create-quarkc/template-quarkc-app-ts/src/components/Header/index.tsx: -------------------------------------------------------------------------------- 1 | import { QuarkElement, Fragment, property, customElement, state, createRef } from "quarkc"; 2 | import style from "./index.css?inline" 3 | 4 | @customElement({ tag: "app-header", style }) 5 | export default class Header extends QuarkElement { 6 | 7 | @state() 8 | version = '1.0.0' 9 | 10 | @state() 11 | menus = [{ 12 | name: 'home', 13 | link: '/' 14 | },{ 15 | name: 'component', 16 | link: '/docs' 17 | }] 18 | 19 | componentDidMount(): void {} 20 | 21 | render() { 22 | return ( 23 |
24 |
25 |
26 |
27 | 28 | 29 | 30 | 31 | { this.version } 32 | 33 |
34 |
35 | 36 |
37 | 48 | 59 |
60 |
61 |
62 |
63 | ); 64 | } 65 | } -------------------------------------------------------------------------------- /packages/router/src/router.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2021 Google LLC 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | import type { ReactiveControllerHost } from '../../../packages/core'; 7 | import { Routes, RouterJumpMethodEnum } from './routes'; 8 | import type {RouteConfig, BaseRouteConfig, RouterJumpDetail } from './routes'; 9 | import { eventBus } from "./eventEmitter"; 10 | 11 | export enum RouterModeEnum { 12 | hash = 'hash', 13 | history = 'history', 14 | } 15 | 16 | // We cache the origin since it can't change 17 | const origin = location.origin || location.protocol + '//' + location.host; 18 | 19 | /** 20 | * A root-level router that installs global event listeners to intercept 21 | * navigation. 22 | * 23 | * This class extends Routes so that it can also have a route configuration. 24 | * 25 | * There should only be one Router instance on a page, since the Router 26 | * installs global event listeners on `window` and `document`. Nested 27 | * routes should be configured with the `Routes` class. 28 | */ 29 | export class Router extends Routes { 30 | mode: RouterModeEnum; 31 | 32 | private _busRouterLinstner = () => { 33 | eventBus.emit("router-host-mounted", this); 34 | } 35 | 36 | private _rootHostListener: ((e: CustomEvent) => void) | undefined; 37 | 38 | constructor( 39 | host: ReactiveControllerHost & HTMLElement, 40 | routes: Array, 41 | options?: { 42 | fallback?: BaseRouteConfig, 43 | mode?: RouterModeEnum, 44 | } 45 | ) { 46 | super(host, routes, options); 47 | this.mode = options?.mode || RouterModeEnum.history; 48 | if (this.mode === RouterModeEnum.hash && !window.location.hash) { 49 | window.history.replaceState({}, '', window.location.href.replace(/#.*/, '') + '#/'); 50 | } 51 | } 52 | 53 | override hostConnected() { 54 | super.hostConnected(); 55 | window.addEventListener('popstate', this._onPopState); 56 | // Kick off routed rendering by going to the current URL 57 | this.goto(this._resolvePath(window.location)); 58 | } 59 | 60 | override hostDisconnected() { 61 | super.hostDisconnected(); 62 | window.removeEventListener('popstate', this._onPopState); 63 | eventBus.off("link-mounted", this._busRouterLinstner); 64 | this.host.removeEventListener(RouterJumpMethodEnum.push, this._rootHostListener!); 65 | this.host.removeEventListener(RouterJumpMethodEnum.replace, this._rootHostListener!); 66 | } 67 | 68 | override hostMounted() { 69 | super.hostMounted(); 70 | eventBus.emit("router-host-mounted", this); 71 | eventBus.on("link-mounted", this._busRouterLinstner); 72 | } 73 | 74 | protected override addListeners() { 75 | super.addListeners(); 76 | this._rootHostListener = (e: CustomEvent) => { 77 | e.stopImmediatePropagation(); 78 | this._changeState(e.detail, e.type as RouterJumpMethodEnum); 79 | }; 80 | this.host.addEventListener(RouterJumpMethodEnum.push, this._rootHostListener, false); 81 | this.host.addEventListener(RouterJumpMethodEnum.replace, this._rootHostListener, false); 82 | } 83 | 84 | private async _changeState(detail: RouterJumpDetail, method?: RouterJumpMethodEnum) { 85 | const { 86 | path, fullPath, callback 87 | } = detail; 88 | const prefix = this.mode === RouterModeEnum.hash ? '#' : ''; 89 | if (method === RouterJumpMethodEnum.replace) { 90 | window.history.replaceState({}, '', prefix + fullPath); 91 | } else { 92 | window.history.pushState({}, '', prefix + fullPath); 93 | } 94 | /* TODO 由于未渲染的childRoute的goto方法在onconnect的时候才执行,无法在父级执行的goto递归中获取 95 | 暂未实现路由进入完成后的callback及错误捕获等 */ 96 | await this.goto(path); 97 | callback && callback(); 98 | } 99 | 100 | private _resolvePath(anchor: HTMLAnchorElement|Location) { 101 | if (this.mode === RouterModeEnum.history) { 102 | return anchor.pathname; 103 | } 104 | return anchor.hash?.replace('#', '').split('?')[0]; 105 | } 106 | 107 | private _onPopState = (_e: PopStateEvent) => { 108 | this.goto(this._resolvePath(window.location)); 109 | }; 110 | } 111 | -------------------------------------------------------------------------------- /packages/create-quarkc/template-quarkc-component/README.md: -------------------------------------------------------------------------------- 1 | # Quark 组件 2 | 3 | 基于本工程,您可以构建属于自己的跨技术栈/无框架 组件。 4 | 5 | ## 如何使用 6 | 7 | ``` 8 | npm install 9 | npm run dev 10 | ``` 11 | 12 | 入口文件为 `src/index.tsx`,这里使用 `vite` 进行开发和生产打包。 13 | 14 | ## 打包产物 15 | 16 | ``` 17 | npm run build 18 | ``` 19 | 20 | 打包后的产出为: `lib/index.js`和`lib/index.umd.cjs`。 21 | 22 | 23 | ## 使用产物 24 | 25 | ### 1、创建组件构建模版工程 26 | 27 | 创建模版 28 | ```bash 29 | npm create quarkc@latest 30 | ``` 31 | 32 | 启动工程 33 | ```bash 34 | npm install 35 | npm start 36 | ``` 37 | 38 | ### 2、自定义你的 Custom Elements(组件/元素) 39 | ```jsx 40 | import { QuarkElement, property, customElement } from "quarkc" 41 | import style from "./index.less?inline" 42 | 43 | @customElement({ tag: "my-element", style }) // 自定义标签/组件、CSS 44 | export default class MyElement extends QuarkElement { 45 | @property() // 外部属性 46 | count 47 | 48 | add = () => { 49 | this.count += 1 50 | } 51 | 52 | render() { 53 | return ( 54 | 55 | ) 56 | } 57 | } 58 | ``` 59 | 60 | ### 3、Build 打包 61 | 62 | 打包默认输出为 UMD / ESM 格式 63 | 64 | ```bash 65 | npm run build 66 | ``` 67 | 68 | 此时,构建产物 `lib/` 下的资源可以直接被任何框架的前端项目中使用。 69 | 70 | ```tree 71 | ./lib 72 | ├── types 73 | | └── install.d.ts 74 | ├── index.js 75 | └── index.umd.js 76 | ``` 77 | 78 | ### 4、使用 79 | 80 | ##### (1)含有工程管理的前端项目(含有package.json/node_modules等文件) 81 | ```jsx 82 | import "./lib/index.js" 83 | 84 | 85 | 86 | 87 | // vue 88 | // 89 | 90 | // react 91 | // 92 | 93 | // svelte 94 | // 95 | 96 | // angular 97 | // 98 | ``` 99 | 100 | ##### (2)无工程管理的前端项目(不含有package.json/node_modules等文件,纯HTML+CSS+JS文件) 101 | 102 | 单个 quarkc 组件,可以直接使用: 103 | 104 | ```html 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | ``` 116 | 117 | 多个 quarkc 组件同时加载,为了共用 quarkc 核心库,您可以选择开启了 `external`: 118 | ```diff 119 | // vite.config.build.ts 120 | export default defineConfig({ 121 | build: { 122 | rollupOptions: { 123 | + external: ['quarkc'], 124 | }, 125 | }, 126 | }); 127 | 128 | ``` 129 | 然后,用下面方式单独加载 `quarkc` 核心库: 130 | ```html 131 | 132 | 133 | 134 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | ``` 154 | 155 | 156 | ## 文档 157 | 158 | 完整文档,请访问 [https://quark-ecosystem.github.io/quarkc-docs](https://quark-ecosystem.github.io/quarkc-docs) 159 | 160 | ### 联系我们 161 | 162 | 添加微信:Sanqi9675 163 | 164 | ### 社区示例 165 | 166 | | 作者 | github 地址 | 截图 / 链接 167 | | ---- | ---- | ----- | 168 | | @xsf0105 | https://github.com/xsf0105/dark-light-element | https://unpkg.com/dark-light-element@latest/demo.html | 169 | | @hellof2e | https://github.com/hellof2e/quark-doc-header | ![1685501041275](https://github.com/hellof2e/quark-core/assets/14307551/24dd5626-e6a9-452c-9c95-c2cdb8891573) https://quarkc.hellobike.com/#/ | 170 | | @yuhaiyang1 | https://github.com/yuhaiyang1/quarkc-time | https://unpkg.com/quark-timer@0.0.2/demo.html | 171 | | @dyf19118 | https://github.com/dyf19118/quark-ui-rate | ![image](https://github.com/hellof2e/quark-cli/assets/14307551/e11e6c49-4c18-4bca-adc3-01a7198ab2e2) | 172 | | @hellof2e | https://github.com/hellof2e/quark-doc-home | ![1686575964690](https://github.com/hellof2e/quark-core/assets/14307551/9618427c-916b-4dfd-b28b-0e8e0f6ce744) | 173 | | @zhangfisher | https://github.com/zhangfisher/lite-tree/tree/master/packages/quark | [点击查看](https://github.com/zhangfisher/lite-tree/blob/master/docs/tree.png?raw=true) | 174 | 175 | -------------------------------------------------------------------------------- /packages/create-quarkc/template-quarkc-component-ts/README.md: -------------------------------------------------------------------------------- 1 | # Quark 组件 2 | 3 | 基于本工程,您可以构建属于自己的跨技术栈/无框架 组件。 4 | 5 | ## 如何使用 6 | 7 | ``` 8 | npm install 9 | npm run dev 10 | ``` 11 | 12 | 入口文件为 `src/index.tsx`,这里使用 `vite` 进行开发和生产打包。 13 | 14 | ## 打包产物 15 | 16 | ``` 17 | npm run build 18 | ``` 19 | 20 | 打包后的产出为: `lib/index.js`和`lib/index.umd.cjs`。 21 | 22 | 23 | ## 使用产物 24 | 25 | ### 1、创建组件构建模版工程 26 | 27 | 创建模版 28 | ```bash 29 | npm create quarkc@latest 30 | ``` 31 | 32 | 启动工程 33 | ```bash 34 | npm install 35 | npm start 36 | ``` 37 | 38 | ### 2、自定义你的 Custom Elements(组件/元素) 39 | ```jsx 40 | import { QuarkElement, property, customElement } from "quarkc" 41 | import style from "./index.less?inline" 42 | 43 | @customElement({ tag: "my-element", style }) // 自定义标签/组件、CSS 44 | export default class MyElement extends QuarkElement { 45 | @property() // 外部属性 46 | count 47 | 48 | add = () => { 49 | this.count += 1 50 | } 51 | 52 | render() { 53 | return ( 54 | 55 | ) 56 | } 57 | } 58 | ``` 59 | 60 | ### 3、Build 打包 61 | 62 | 打包默认输出为 UMD / ESM 格式 63 | 64 | ```bash 65 | npm run build 66 | ``` 67 | 68 | 此时,构建产物 `lib/` 下的资源可以直接被任何框架的前端项目中使用。 69 | 70 | ```tree 71 | ./lib 72 | ├── types 73 | | └── install.d.ts 74 | ├── index.js 75 | └── index.umd.js 76 | ``` 77 | 78 | ### 4、使用 79 | 80 | ##### (1)含有工程管理的前端项目(含有package.json/node_modules等文件) 81 | ```jsx 82 | import "./lib/index.js" 83 | 84 | 85 | 86 | 87 | // vue 88 | // 89 | 90 | // react 91 | // 92 | 93 | // svelte 94 | // 95 | 96 | // angular 97 | // 98 | ``` 99 | 100 | ##### (2)无工程管理的前端项目(不含有package.json/node_modules等文件,纯HTML+CSS+JS文件) 101 | 102 | 单个 quarkc 组件,可以直接使用: 103 | 104 | ```html 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | ``` 116 | 117 | 多个 quarkc 组件同时加载,为了共用 quarkc 核心库,您可以选择开启了 `external`: 118 | ```diff 119 | // vite.config.build.ts 120 | export default defineConfig({ 121 | build: { 122 | rollupOptions: { 123 | + external: ['quarkc'], 124 | }, 125 | }, 126 | }); 127 | 128 | ``` 129 | 然后,用下面方式单独加载 `quarkc` 核心库: 130 | ```html 131 | 132 | 133 | 134 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | ``` 154 | 155 | 156 | ## 文档 157 | 158 | 完整文档,请访问 [https://quark-ecosystem.github.io/quarkc-docs](https://quark-ecosystem.github.io/quarkc-docs) 159 | 160 | ### 联系我们 161 | 162 | 添加微信:Sanqi9675 163 | 164 | ### 社区示例 165 | 166 | | 作者 | github 地址 | 截图 / 链接 167 | | ---- | ---- | ----- | 168 | | @xsf0105 | https://github.com/xsf0105/dark-light-element | https://unpkg.com/dark-light-element@latest/demo.html | 169 | | @hellof2e | https://github.com/hellof2e/quark-doc-header | ![1685501041275](https://github.com/hellof2e/quark-core/assets/14307551/24dd5626-e6a9-452c-9c95-c2cdb8891573) https://quarkc.hellobike.com/#/ | 170 | | @yuhaiyang1 | https://github.com/yuhaiyang1/quarkc-time | https://unpkg.com/quark-timer@0.0.2/demo.html | 171 | | @dyf19118 | https://github.com/dyf19118/quark-ui-rate | ![image](https://github.com/hellof2e/quark-cli/assets/14307551/e11e6c49-4c18-4bca-adc3-01a7198ab2e2) | 172 | | @hellof2e | https://github.com/hellof2e/quark-doc-home | ![1686575964690](https://github.com/hellof2e/quark-core/assets/14307551/9618427c-916b-4dfd-b28b-0e8e0f6ce744) | 173 | | @zhangfisher | https://github.com/zhangfisher/lite-tree/tree/master/packages/quark | [点击查看](https://github.com/zhangfisher/lite-tree/blob/master/docs/tree.png?raw=true) | 174 | 175 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | const { builtinModules } = require('node:module') 3 | const { defineConfig } = require('eslint-define-config') 4 | 5 | module.exports = defineConfig({ 6 | root: true, 7 | extends: [ 8 | 'eslint:recommended', 9 | 'plugin:n/recommended', 10 | 'plugin:@typescript-eslint/recommended', 11 | 'plugin:regexp/recommended', 12 | ], 13 | ignorePatterns: ['packages/create-quarkc/template-**'], 14 | plugins: ['import', 'regexp'], 15 | parser: '@typescript-eslint/parser', 16 | parserOptions: { 17 | sourceType: 'module', 18 | ecmaVersion: 2021, 19 | }, 20 | rules: { 21 | eqeqeq: ['warn', 'always', { null: 'never' }], 22 | 'no-debugger': ['error'], 23 | 'no-empty': ['warn', { allowEmptyCatch: true }], 24 | 'no-process-exit': 'off', 25 | 'no-useless-escape': 'off', 26 | 'prefer-const': [ 27 | 'warn', 28 | { 29 | destructuring: 'all', 30 | }, 31 | ], 32 | 'n/no-process-exit': 'off', 33 | 'n/no-missing-import': 'off', 34 | 'n/no-missing-require': [ 35 | 'error', 36 | { 37 | // for try-catching yarn pnp 38 | allowModules: ['pnpapi', 'quarkc'], 39 | tryExtensions: ['.ts', '.js', '.jsx', '.tsx', '.d.ts'], 40 | }, 41 | ], 42 | 'n/no-extraneous-import': [ 43 | 'error', 44 | { 45 | allowModules: ['quarkc', 'less', 'sass', 'unbuild'], 46 | }, 47 | ], 48 | 'n/no-extraneous-require': [ 49 | 'error', 50 | { 51 | allowModules: ['vite'], 52 | }, 53 | ], 54 | 'n/no-deprecated-api': 'off', 55 | 'n/no-unpublished-import': 'off', 56 | 'n/no-unpublished-require': 'off', 57 | 'n/no-unsupported-features/es-syntax': 'off', 58 | 59 | '@typescript-eslint/ban-ts-comment': 'error', 60 | '@typescript-eslint/ban-types': 'off', // TODO: we should turn this on in a new PR 61 | '@typescript-eslint/explicit-module-boundary-types': [ 62 | 'error', 63 | { allowArgumentsExplicitlyTypedAsAny: true }, 64 | ], 65 | '@typescript-eslint/no-empty-function': [ 66 | 'error', 67 | { allow: ['arrowFunctions'] }, 68 | ], 69 | '@typescript-eslint/no-empty-interface': 'off', 70 | '@typescript-eslint/no-explicit-any': 'off', // maybe we should turn this on in a new PR 71 | '@typescript-eslint/no-extra-semi': 'off', // conflicts with prettier 72 | '@typescript-eslint/no-inferrable-types': 'off', 73 | '@typescript-eslint/no-non-null-assertion': 'off', // maybe we should turn this on in a new PR 74 | '@typescript-eslint/no-unused-vars': 'off', // maybe we should turn this on in a new PR 75 | '@typescript-eslint/no-var-requires': 'off', 76 | '@typescript-eslint/consistent-type-imports': [ 77 | 'error', 78 | { prefer: 'type-imports' }, 79 | ], 80 | 81 | 'import/no-nodejs-modules': [ 82 | 'error', 83 | { allow: builtinModules.map((mod) => `node:${mod}`) }, 84 | ], 85 | 'import/no-duplicates': 'error', 86 | 'import/order': 'error', 87 | 'sort-imports': [ 88 | 'error', 89 | { 90 | ignoreCase: false, 91 | ignoreDeclarationSort: true, 92 | ignoreMemberSort: false, 93 | memberSyntaxSortOrder: ['none', 'all', 'multiple', 'single'], 94 | allowSeparatedGroups: false, 95 | }, 96 | ], 97 | 98 | 'regexp/no-contradiction-with-assertion': 'error', 99 | }, 100 | overrides: [ 101 | { 102 | files: ['packages/**'], 103 | excludedFiles: '**/__tests__/**', 104 | rules: { 105 | 'no-restricted-globals': [ 106 | 'error', 107 | 'require', 108 | '__dirname', 109 | '__filename', 110 | ], 111 | }, 112 | }, 113 | { 114 | files: ['packages/create-quarkc/template-*/**', '**/build.config.ts'], 115 | rules: { 116 | 'no-undef': 'off', 117 | 'n/no-missing-import': 'off', 118 | '@typescript-eslint/explicit-module-boundary-types': 'off', 119 | }, 120 | }, 121 | { 122 | files: ['*.js', '*.mjs', '*.cjs'], 123 | rules: { 124 | '@typescript-eslint/explicit-module-boundary-types': 'off', 125 | }, 126 | }, 127 | { 128 | files: ['*.d.ts'], 129 | rules: { 130 | '@typescript-eslint/triple-slash-reference': 'off', 131 | }, 132 | }, 133 | ], 134 | reportUnusedDisableDirectives: true, 135 | }) 136 | -------------------------------------------------------------------------------- /packages/core/src/core/diff/props.js: -------------------------------------------------------------------------------- 1 | import { IS_NON_DIMENSIONAL } from '../constants'; 2 | import options from '../options'; 3 | import { updateDomAttr } from '../util'; 4 | 5 | /** 6 | * Diff the old and new properties of a VNode and apply changes to the DOM node 7 | * @param dom The DOM node to apply 8 | * changes to 9 | * @param {object} newProps The new props 10 | * @param {object} oldProps The old props 11 | * @param {boolean} isSvg Whether or not this node is an SVG node 12 | */ 13 | export function diffProps(dom, newProps, oldProps, isSvg) { 14 | let i; 15 | 16 | for (i in oldProps) { 17 | if (i !== 'children' && i !== 'key' && !(i in newProps)) { 18 | setProperty(dom, i, null, oldProps[i], isSvg); 19 | } 20 | } 21 | 22 | for (i in newProps) { 23 | if ( 24 | i !== 'children' && 25 | i !== 'key' && 26 | i !== 'value' && 27 | i !== 'checked' && 28 | oldProps[i] !== newProps[i] 29 | ) { 30 | setProperty(dom, i, newProps[i], oldProps[i], isSvg); 31 | } 32 | } 33 | } 34 | 35 | function setStyle(style, key, value) { 36 | if (key[0] === '-') { 37 | style.setProperty(key, value == null ? '' : value); 38 | } else if (value == null) { 39 | style[key] = ''; 40 | } else if (typeof value != 'number' || IS_NON_DIMENSIONAL.test(key)) { 41 | style[key] = value; 42 | } else { 43 | style[key] = value + 'px'; 44 | } 45 | } 46 | 47 | /** 48 | * Set a property value on a DOM node 49 | * @param dom The DOM node to modify 50 | * @param {string} name The name of the property to set 51 | * @param value The value to set the property to 52 | * @param oldValue The old value the property had 53 | * @param {boolean} isSvg Whether or not this DOM node is an SVG node or not 54 | */ 55 | export function setProperty(dom, name, value, oldValue, isSvg) { 56 | let useCapture; 57 | 58 | o: if (name === 'style') { 59 | if (typeof value == 'string') { 60 | dom.style.cssText = value; 61 | } else { 62 | if (typeof oldValue == 'string') { 63 | dom.style.cssText = oldValue = ''; 64 | } 65 | 66 | if (oldValue) { 67 | for (name in oldValue) { 68 | if (!(value && name in value)) { 69 | setStyle(dom.style, name, ''); 70 | } 71 | } 72 | } 73 | 74 | if (value) { 75 | for (name in value) { 76 | if (!oldValue || value[name] !== oldValue[name]) { 77 | setStyle(dom.style, name, value[name]); 78 | } 79 | } 80 | } 81 | } 82 | } 83 | // Benchmark for comparison: https://esbench.com/bench/574c954bdb965b9a00965ac6 84 | else if (name[0] === 'o' && name[1] === 'n') { 85 | useCapture = name !== (name = name.replace(/Capture$/, '')); 86 | 87 | // Infer correct casing for DOM built-in events: 88 | if (name.toLowerCase() in dom) name = name.toLowerCase().slice(2); 89 | else name = name.slice(2); 90 | 91 | if (!dom._listeners) dom._listeners = {}; 92 | dom._listeners[name + useCapture] = value; 93 | 94 | if (value) { 95 | if (!oldValue) { 96 | const handler = useCapture ? eventProxyCapture : eventProxy; 97 | dom.addEventListener(name, handler, useCapture); 98 | } 99 | } else { 100 | const handler = useCapture ? eventProxyCapture : eventProxy; 101 | dom.removeEventListener(name, handler, useCapture); 102 | } 103 | } else if (name !== 'dangerouslySetInnerHTML') { 104 | if (isSvg) { 105 | // Normalize incorrect prop usage for SVG: 106 | // - xlink:href / xlinkHref --> href (xlink:href was removed from SVG and isn't needed) 107 | // - className --> class 108 | name = name.replace(/xlink(H|:h)/, 'h').replace(/sName$/, 's'); 109 | } else if ( 110 | name !== 'width' && 111 | name !== 'height' && 112 | name !== 'href' && 113 | name !== 'list' && 114 | name !== 'form' && 115 | // Default value in browsers is `-1` and an empty string is 116 | // cast to `0` instead 117 | name !== 'tabIndex' && 118 | name !== 'download' && 119 | name in dom 120 | ) { 121 | try { 122 | // * update internal props here 123 | if (dom._isInternalProp?.(name)) { 124 | dom[name] = value; 125 | } else { 126 | dom[name] = value == null ? '' : value; 127 | } 128 | 129 | // labelled break is 1b smaller here than a return statement (sorry) 130 | break o; 131 | } catch (e) { } 132 | } 133 | 134 | // ARIA-attributes have a different notion of boolean values. 135 | // The value `false` is different from the attribute not 136 | // existing on the DOM, so we can't remove it. For non-boolean 137 | // ARIA-attributes we could treat false as a removal, but the 138 | // amount of exceptions would cost us too many bytes. On top of 139 | // that other VDOM frameworks also always stringify `false`. 140 | 141 | if (typeof value === 'function') { 142 | // never serialize functions as attribute values 143 | } else { 144 | updateDomAttr(dom, name, value) 145 | } 146 | } 147 | } 148 | 149 | /** 150 | * Proxy an event to hooked event handlers 151 | * @param {Event} e The event object from the browser 152 | * @private 153 | */ 154 | function eventProxy(e) { 155 | return this._listeners[e.type + false](options.event ? options.event(e) : e); 156 | } 157 | 158 | function eventProxyCapture(e) { 159 | return this._listeners[e.type + true](options.event ? options.event(e) : e); 160 | } 161 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Quarkc —— 无框架,跨框架!

2 | 3 |

4 | Total Downloads 5 | 6 | Published on NPM 7 | 8 | License 9 |

10 | 11 | 12 |

13 | 简体中文 | 14 | 15 | English 16 | 17 |

18 | 19 |

20 | 21 |

22 | 23 | ## 介绍 24 | 25 | Quarkc(Quark core缩写) 是一个拥有完美开发体验的 web components 工具(jsx + web components)。通过它,您可以开发 [跨框架组件](https://github.com/hellof2e/quark-core/tree/main/packages/create-quarkc/template-quarkc-component-ts) 或 [独立页面](https://github.com/hellof2e/quark-core/tree/main/packages/create-quarkc/template-quarkc-app-ts)。 26 | 27 | ## 特性 28 | 29 | * 无框架,组件可以在任何框架或无框架的环境下使用,让你的代码更具复用性 30 | * 产物体积极小,性能接近浏览器原生元素 31 | * Web Components, Simple, Fast! 32 | * 浏览器原生API,组件可以跨技术栈使用 33 | * 没有前端框架 Runtime,Web 组件体积小到极致 34 | * Shadow DOM 与 Virtual DOM 的完美融合 35 | * 组件直接解耦,独立打磨,按需引用 36 | 37 | 38 | ## 使用 39 | 40 | ### 1、创建组件构建模版工程 41 | 42 | 创建模版 43 | ```bash 44 | npm create quarkc@latest 45 | ``` 46 | 47 | 启动工程 48 | ```bash 49 | npm install 50 | npm start 51 | ``` 52 | 53 | ### 2、自定义你的 Custom Elements(组件/元素) 54 | ```jsx 55 | import { QuarkElement, property, customElement } from "quarkc" 56 | import style from "./index.less?inline" 57 | 58 | @customElement({ tag: "my-element", style }) // 自定义标签/组件、CSS 59 | export default class MyElement extends QuarkElement { 60 | @property() // 外部属性 61 | count 62 | 63 | add = () => { 64 | this.count += 1 65 | } 66 | 67 | render() { 68 | return ( 69 | 70 | ) 71 | } 72 | } 73 | ``` 74 | 75 | ### 3、Build 打包 76 | 77 | 打包默认输出为 UMD / ESM 格式 78 | 79 | ```bash 80 | npm run build 81 | ``` 82 | 83 | 此时,构建产物 `lib/` 下的资源可以直接被任何框架的前端项目中使用。 84 | 85 | ### 4、使用 86 | 87 | #### 场景1:含有工程管理的前端项目(含有package.json/node_modules等文件) 88 | ```jsx 89 | import "./lib/your-element" 90 | 91 | 92 | 93 | 94 | // vue 95 | // 96 | 97 | // react 98 | // 99 | 100 | // svelte 101 | // 102 | 103 | // angular 104 | // 105 | ``` 106 | 107 | #### 场景2:无工程管理的前端项目(不含有package.json/node_modules等文件,纯HTML+CSS+JS文件) 108 | 109 | 单个 quarkc 组件,可以直接使用: 110 | 111 | ```html 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | ``` 123 | 124 | 多个 quarkc 组件同时加载,为了共用 quarkc 核心库,您可以选择开启了 `external`: 125 | ```diff 126 | // vite.config.build.ts 127 | export default defineConfig({ 128 | build: { 129 | rollupOptions: { 130 | + external: ['quarkc'], 131 | }, 132 | }, 133 | }); 134 | 135 | ``` 136 | 然后,用下面方式单独加载 `quarkc` 核心库: 137 | ```html 138 | 139 | 140 | 141 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | ``` 161 | 162 | 163 | ## 文档 164 | 165 | 完整文档,请访问 [https://quark-ecosystem.github.io/quarkc-docs](https://quark-ecosystem.github.io/quarkc-docs) 166 | 167 | ### 联系我们 168 | 169 | 添加微信:Sanqi9675 170 | 171 | ### 社区示例 172 | 173 | | 作者 | github 地址 | 截图 / 链接 174 | | ---- | ---- | ----- | 175 | | @xsf0105 | https://xsf0105.github.io/piano/ | https://xsf0105.github.io/piano/ | 176 | | @xsf0105 | https://github.com/xsf0105/dark-light-element | https://unpkg.com/dark-light-element@latest/demo.html | 177 | | @hellof2e | https://github.com/hellof2e/quark-doc-header | ![1685501041275](https://github.com/hellof2e/quark-core/assets/14307551/24dd5626-e6a9-452c-9c95-c2cdb8891573) https://quarkc.hellobike.com/#/ | 178 | | @yuhaiyang1 | https://github.com/yuhaiyang1/quarkc-time | https://unpkg.com/quark-timer@0.0.2/demo.html | 179 | | @dyf19118 | https://github.com/dyf19118/quark-ui-rate | ![image](https://github.com/hellof2e/quark-cli/assets/14307551/e11e6c49-4c18-4bca-adc3-01a7198ab2e2) | 180 | | @hellof2e | https://github.com/hellof2e/quark-doc-home | ![1686575964690](https://github.com/hellof2e/quark-core/assets/14307551/9618427c-916b-4dfd-b28b-0e8e0f6ce744) | 181 | | @zhangfisher | https://github.com/zhangfisher/lite-tree/tree/master/packages/quark | [点击查看](https://github.com/zhangfisher/lite-tree/blob/master/docs/tree.png?raw=true) | 182 | 183 | 184 | ## License 185 | 186 | [MIT LICENSE](./LICENSE) 187 | -------------------------------------------------------------------------------- /demo4gluang/src/app-article/index.tsx: -------------------------------------------------------------------------------- 1 | import { QuarkElement, customElement } from "quarkc" 2 | import style from "./index.less?inline" 3 | 4 | @customElement({ tag: "app-article", style }) 5 | class MyComponent extends QuarkElement { 6 | render() { 7 | return ( 8 |
9 |

10 | Twenty-Five Hundred years ago, Sun Tzu wrote this classic book of 11 | military strategy based on Chinese warfare and military thought. 12 |

13 |

1. Laying Plans

14 |

Sun Tzu said: The art of war is of vital importance to the State.

15 |

16 | It is a matter of life and death, a road either to safety or to ruin. 17 | Hence it is a subject of inquiry which can on no account be neglected. 18 |

19 |

20 | The art of war, then, is governed by five constant factors, to be taken 21 | into account in one's deliberations, when seeking to determine the 22 | conditions obtaining in the field. 23 |

24 |

25 | These are: (1) The Moral Law; (2) Heaven; (3) Earth; (4) The Commander; 26 | (5) Method and discipline. 27 |

28 |

29 | The MORAL LAW causes the people to be in complete accord with their 30 | ruler, so that they will follow him regardless of their lives, 31 | undismayed by any danger. 32 |

33 |

HEAVEN signifies night and day, cold and heat, times and seasons.

34 |

35 | EARTH comprises distances, great and small; danger and security; open 36 | ground and narrow passes; the chances of life and death. 37 |

38 |

39 | The COMMANDER stands for the virtues of wisdom, sincerity, benevolence, 40 | courage and strictness. 41 |

42 |

43 | By METHOD AND DISCIPLINE are to be understood the marshaling of the army 44 | in its proper subdivisions, the graduations of rank among the officers, 45 | the maintenance of roads by which supplies may reach the army, and the 46 | control of military expenditure. 47 |

48 |

49 | These five heads should be familiar to every general: he who knows them 50 | will be victorious; he who knows them not will fail. 51 |

52 |

53 | Therefore, in your deliberations, when seeking to determine the military 54 | conditions, let them be made the basis of a comparison, in this wise: 55 |

56 |

(1) Which of the two sovereigns is imbued with the Moral law?

57 |

(2) Which of the two generals has most ability?

58 |

(3) With whom lie the advantages derived from Heaven and Earth?

59 |

(4) On which side is discipline most rigorously enforced?

60 |

(5) Which army is stronger?

61 |

(6) On which side are officers and men more highly trained?

62 |

63 | (7) In which army is there the greater constancy both in reward and 64 | punishment? 65 |

66 |

67 | By means of these seven considerations I can forecast victory or defeat. 68 |

69 |

70 | The general that hearkens to my counsel and acts upon it, will conquer: 71 | let such a one be retained in command! The general that hearkens not to 72 | my counsel nor acts upon it, will suffer defeat: let such a one be 73 | dismissed! 74 |

75 |

76 | While heeding the profit of my counsel, avail yourself also of any 77 | helpful circumstances over and beyond the ordinary rules. 78 |

79 |

80 | According as circumstances are favorable, one should modify one's plans. 81 |

82 |

All warfare is based on deception.

83 |

84 | Hence, when able to attack, we must seem unable; when using our forces, 85 | we must seem inactive; when we are near, we must make the enemy believe 86 | we are far away; when far away, we must make him believe we are near. 87 |

88 |

Hold out baits to entice the enemy. Feign disorder, and crush him.

89 |

90 | If he is secure at all points, be prepared for him. If he is in superior 91 | strength, evade him. 92 |

93 |

94 | If your opponent is of choleric temper, seek to irritate him. Pretend to 95 | be weak, that he may grow arrogant. 96 |

97 |

If he is taking his ease, give him no rest.

98 |

If his forces are united, separate them.

99 |

100 | Attack him where he is unprepared, appear where you are not expected. 101 |

102 |

103 | These military devices, leading to victory, must not be divulged 104 | beforehand. 105 |

106 |

107 | Now the general who wins a battle makes many calculations in his temple 108 | ere the battle is fought. 109 |

110 |

111 | The general who loses a battle makes but few calculations beforehand. 112 | Thus do many calculations lead to victory, and few calculations to 113 | defeat: how much more no calculation at all! It is by attention to this 114 | point that I can foresee who is likely to win or lose. 115 |

116 |
117 | ); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /packages/core/README.md: -------------------------------------------------------------------------------- 1 |

2 | vite-plugin-dev-inspector 3 |

4 | 5 |

Quarkc —— 无框架,跨框架!

6 | 7 |

8 | Total Downloads 9 | 10 | Published on NPM 11 | 12 | License 13 |

14 | 15 | 16 |

17 | 简体中文 | 18 | 19 | English 20 | 21 |

22 | 23 |

24 | 25 |

26 | 27 | ## 介绍 28 | 29 | Quarkc(Quark core缩写) 是一个拥有完美开发体验的 web components 工具(jsx + web components)。通过它,您可以开发 [跨框架组件](https://github.com/hellof2e/quark-core/tree/main/packages/create-quarkc/template-quarkc-component-ts) 或 [独立页面](https://github.com/hellof2e/quark-core/tree/main/packages/create-quarkc/template-quarkc-app-ts)。 30 | 31 | ## 特性 32 | 33 | * 无框架,组件可以在任何框架或无框架的环境下使用,让你的代码更具复用性 34 | * 产物体积极小,性能接近浏览器原生元素 35 | * Web Components, Simple, Fast! 36 | * 浏览器原生API,组件可以跨技术栈使用 37 | * 没有前端框架 Runtime,Web 组件体积小到极致 38 | * Shadow DOM 与 Virtual DOM 的完美融合 39 | * 组件直接解耦,独立打磨,按需引用 40 | 41 | 42 | ## 使用 43 | 44 | ### 1、创建组件构建模版工程 45 | 46 | 创建模版 47 | ```bash 48 | npm create quarkc@latest 49 | ``` 50 | 51 | 启动工程 52 | ```bash 53 | npm install 54 | npm start 55 | ``` 56 | 57 | ### 2、自定义你的 Custom Elements(组件/元素) 58 | ```jsx 59 | import { QuarkElement, property, customElement } from "quarkc" 60 | import style from "./index.less?inline" 61 | 62 | @customElement({ tag: "my-element", style }) // 自定义标签/组件、CSS 63 | export default class MyElement extends QuarkElement { 64 | @property() // 外部属性 65 | count 66 | 67 | add = () => { 68 | this.count += 1 69 | } 70 | 71 | render() { 72 | return ( 73 | 74 | ) 75 | } 76 | } 77 | ``` 78 | 79 | ### 3、Build 打包 80 | 81 | 打包默认输出为 UMD / ESM 格式 82 | 83 | ```bash 84 | npm run build 85 | ``` 86 | 87 | 此时,构建产物 `lib/` 下的资源可以直接被任何框架的前端项目中使用。 88 | 89 | ### 4、使用 90 | 91 | ##### (1)含有工程管理的前端项目(含有package.json/node_modules等文件) 92 | ```jsx 93 | import "./lib/your-element" 94 | 95 | 96 | 97 | 98 | // vue 99 | // 100 | 101 | // react 102 | // 103 | 104 | // svelte 105 | // 106 | 107 | // angular 108 | // 109 | ``` 110 | 111 | ##### (2)无工程管理的前端项目(不含有package.json/node_modules等文件,纯HTML+CSS+JS文件) 112 | 113 | 单个 quarkc 组件,可以直接使用: 114 | 115 | ```html 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | ``` 127 | 128 | 多个 quarkc 组件同时加载,为了共用 quarkc 核心库,您可以选择开启了 `external`: 129 | ```diff 130 | // vite.config.build.ts 131 | export default defineConfig({ 132 | build: { 133 | rollupOptions: { 134 | + external: ['quarkc'], 135 | }, 136 | }, 137 | }); 138 | 139 | ``` 140 | 然后,用下面方式单独加载 `quarkc` 核心库: 141 | ```html 142 | 143 | 144 | 145 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | ``` 165 | 166 | 167 | ## 文档 168 | 169 | 完整文档,请访问 [https://quark-ecosystem.github.io/quarkc-docs](https://quark-ecosystem.github.io/quarkc-docs) 170 | 171 | ### 联系我们 172 | 173 | 添加微信:Sanqi9675 174 | 175 | ### 社区示例 176 | 177 | | 作者 | github 地址 | 截图 / 链接 178 | | ---- | ---- | ----- | 179 | | @xsf0105 | https://xsf0105.github.io/piano/ | https://xsf0105.github.io/piano/ | 180 | | @xsf0105 | https://github.com/xsf0105/dark-light-element | https://unpkg.com/dark-light-element@latest/demo.html | 181 | | @hellof2e | https://github.com/hellof2e/quark-doc-header | ![1685501041275](https://github.com/hellof2e/quark-core/assets/14307551/24dd5626-e6a9-452c-9c95-c2cdb8891573) https://quarkc.hellobike.com/#/ | 182 | | @yuhaiyang1 | https://github.com/yuhaiyang1/quarkc-time | https://unpkg.com/quark-timer@0.0.2/demo.html | 183 | | @dyf19118 | https://github.com/dyf19118/quark-ui-rate | ![image](https://github.com/hellof2e/quark-cli/assets/14307551/e11e6c49-4c18-4bca-adc3-01a7198ab2e2) | 184 | | @hellof2e | https://github.com/hellof2e/quark-doc-home | ![1686575964690](https://github.com/hellof2e/quark-core/assets/14307551/9618427c-916b-4dfd-b28b-0e8e0f6ce744) | 185 | | @zhangfisher | https://github.com/zhangfisher/lite-tree/tree/master/packages/quark | [点击查看](https://github.com/zhangfisher/lite-tree/blob/master/docs/tree.png?raw=true) | 186 | 187 | 188 | ## License 189 | 190 | [MIT LICENSE](./LICENSE) -------------------------------------------------------------------------------- /README.en-US.md: -------------------------------------------------------------------------------- 1 | 2 |

3 | 4 | 5 | 6 |

7 |

Quarkc

8 |
9 | 10 | Quarkc,跨技术栈/原生组件构建工具。 11 | 12 |
13 | 14 |

15 | Total Downloads 16 | 17 | Published on NPM 18 | 19 | License 20 |

21 | 22 | 23 |

24 | 25 | 简体中文 26 | 27 | | English 28 |

29 | 30 | ### outstanding case 31 | 32 | | author | github address | screenshot / link 33 | | ---- | ---- | ----- | 34 | | @xsf0105 | https://xsf0105.github.io/piano/ | https://xsf0105.github.io/piano/ | 35 | | @xsf0105 | https://github.com/xsf0105/dark-light-element | https://unpkg.com/dark-light-element@latest/demo.html | 36 | | @yuhaiyang1 | https://github.com/yuhaiyang1/quarkc-time | https://unpkg.com/quark-timer@0.0.2/demo.html | 37 | | @hellof2e | https://github.com/hellof2e/quark-doc-header | ![1685501041275](https://github.com/hellof2e/quark-core/assets/14307551/24dd5626-e6a9-452c-9c95-c2cdb8891573 ) https://quarkc.hellobike.com/#/ | 38 | | @dyf19118 | https://github.com/dyf19118/quark-ui-rate | ![image](https://github.com/hellof2e/quark-cli/assets/14307551/e11e6c49-4c18-4bca-adc3 -01a7198ab2e2) | 39 | | @hellof2e | https://github.com/hellof2e/quark-doc-home | ![1686575964690](https://github.com/hellof2e/quark-core/assets/14307551/9618427c-916b-4dfd-b28b-0e8e0f6ce744 ) | 40 | 41 | 42 | ## introduce 43 | 44 | Quarkc (Quark core abbreviation) is a web components framework with perfect development experience. With it, you can develop standard **cross-framework components**. 45 | 46 | ## Why Quarkc? 47 | 48 | Background 1: [History of the front end] 49 | The front-end has been developed for many years. Regardless of the size of the company, there are generally various technology stacks (React, Angular, Jq, Vue) / different versions of the same technology stack (Vue2, Vue3). If you want to develop a common component (for example: marketing pop-up window), the workload is double+ (different technical frameworks need to be developed/maintained/launched separately, and different versions of the same technology may also need to be developed/maintained/launched separately) 50 | 51 | Background 2: [The future of the front end] 52 | The front-end framework will continue to iterate/develop, and there will be new versions and new frameworks. Using Quarkc to develop "universal components" will not update and iterate along with the "wave of front-end frameworks" (greatly reducing component development/maintenance costs). 53 | 54 | The above background determines that the development and maintenance costs of **front-end general-purpose components** are relatively high. 55 | 56 | ## Quarkc target 57 | 58 | Let web components implement technology stack independent! 59 | 60 | ## use 61 | 62 | ### Component starter template 63 | 64 | 1. Engineering installation 65 | ```bash 66 | npm create quarkc@latest 67 | cd project-name 68 | 69 | npm install 70 | npm start 71 | ``` 72 | 73 | 2. Custom components 74 | ```jsx 75 | import { QuarkElement, property, customElement } from "quarkc" 76 | import style from "./index.less?inline" 77 | 78 | @customElement({ tag: "my-element", style }) // custom tag/component, CSS 79 | export default class MyElement extends QuarkElement { 80 | @property() // external property 81 | count 82 | 83 | add = () => { 84 | this.count += 1 85 | } 86 | 87 | render() { 88 | return ( 89 | 90 | ) 91 | } 92 | } 93 | ``` 94 | 95 | 3. use 96 | 97 | All kinds of tech stacks work. 98 | ```html 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | ``` 113 | 114 | ### Component packaging 115 | 116 | Package default output is UMD / ESM format 117 | 118 | ```bash 119 | npm run build 120 | ``` 121 | 122 | At this point, the resources under the build product `lib/` can be used directly in the project. (Any front-end project can be used~) 123 | 124 | ```jsx 125 | import "your-element" 126 | 127 | 128 | ``` 129 | 130 | ### Component publishing 131 | 132 | Components can be published to npm, installed with: 133 | 134 | ```bash 135 | npm install your-element 136 | ``` 137 | 138 | Can be used as a CDN 139 | 140 | ```html 141 | 142 | 143 | ``` 144 | 145 | Also available as ES Module (recommended) 146 | ```js 147 | import "your-element" 148 | ``` 149 | 150 | For more details about publishing, click [Publishing](https://quarkc.hellobike.com/#/zh-CN/docs/publishing) 151 | 152 | ### Features 153 | 154 | * **Cross-Technology Stack**: Components can be used in any frame or frameless environment, making your code more reusable 155 | * **The component size is very small and the performance is extremely high**: Because Quarkc uses the browser's native API, your component can achieve optimal performance and small size 156 | * Web Components, Simple, Fast! 157 | * Browser native API, components can be used across technology stacks 158 | * There is no front-end framework Runtime, and the size of Web components is extremely small 159 | * **High performance** design, integration of Shadow DOM and Virtual DOM 160 | * Components are directly decoupled, polished independently, and referenced on demand 161 | 162 | ### Performance reference 163 | 164 | Screenshot of a slightly complex component page running score: 165 | 166 | image 167 | 168 | ### Documentation 169 | 170 | For full documentation, please visit [quarkc.hellobike.com](https://quarkc.hellobike.com) 171 | -------------------------------------------------------------------------------- /packages/core/README.en-US.md: -------------------------------------------------------------------------------- 1 | 2 |

3 | 4 | 5 | 6 |

7 |

Quarkc

8 |
9 | 10 | Quarkc, a cross technology stack / native component building tool. 11 | 12 |
13 | 14 |

15 | Total Downloads 16 | 17 | Published on NPM 18 | 19 | License 20 |

21 | 22 | 23 |

24 | 25 | 简体中文 26 | 27 | | English 28 |

29 | 30 | ### outstanding case 31 | 32 | | author | github address | screenshot / link 33 | | ---- | ---- | ----- | 34 | | @xsf0105 | https://xsf0105.github.io/piano/ | https://xsf0105.github.io/piano/ | 35 | | @yuhaiyang1 | https://github.com/yuhaiyang1/quarkc-time | https://unpkg.com/quark-timer@0.0.2/demo.html | 36 | | @khno | https://github.com/khno/quark-element-demo-celebrate | https://unpkg.com/quarkc-demo-celebrate@latest/demo.html | 37 | | @hellof2e | https://github.com/hellof2e/quark-doc-header | ![1685501041275](https://github.com/hellof2e/quark-core/assets/14307551/24dd5626-e6a9-452c-9c95-c2cdb8891573 ) https://quarkc.hellobike.com/#/ | 38 | | @xsf0105 | https://github.com/xsf0105/dark-light-element | https://unpkg.com/dark-light-element@latest/demo.html | 39 | | @dyf19118 | https://github.com/dyf19118/quark-ui-rate | ![image](https://github.com/hellof2e/quark-cli/assets/14307551/e11e6c49-4c18-4bca-adc3 -01a7198ab2e2) | 40 | | @hellof2e | https://github.com/hellof2e/quark-doc-home | ![1686575964690](https://github.com/hellof2e/quark-core/assets/14307551/9618427c-916b-4dfd-b28b-0e8e0f6ce744 ) | 41 | | @zhangfisher | https://github.com/zhangfisher/lite-tree/tree/master/packages/quark | [click to view](https://github.com/zhangfisher/lite-tree/blob/master/docs/tree.png?raw=true) | 42 | 43 | 44 | ## introduce 45 | 46 | Quarkc (Quark core abbreviation) is a web components framework with perfect development experience. With it, you can develop standard **cross-framework components**. 47 | 48 | ## Why Quarkc? 49 | 50 | Background 1: [History of the front end] 51 | The front-end has been developed for many years. Regardless of the size of the company, there are generally various technology stacks (React, Angular, Jq, Vue) / different versions of the same technology stack (Vue2, Vue3). If you want to develop a common component (for example: marketing pop-up window), the workload is double+ (different technical frameworks need to be developed/maintained/launched separately, and different versions of the same technology may also need to be developed/maintained/launched separately) 52 | 53 | Background 2: [The future of the front end] 54 | The front-end framework will continue to iterate/develop, and there will be new versions and new frameworks. Using Quarkc to develop "universal components" will not update and iterate along with the "wave of front-end frameworks" (greatly reducing component development/maintenance costs). 55 | 56 | The above background determines that the development and maintenance costs of **front-end general-purpose components** are relatively high. 57 | 58 | ## Quarkc target 59 | 60 | Let web components implement technology stack independent! 61 | 62 | ## use 63 | 64 | ### Component starter template 65 | 66 | 1. Engineering installation 67 | ```bash 68 | npm create quarkc@latest 69 | cd project-name 70 | 71 | npm install 72 | npm start 73 | ``` 74 | 75 | 2. Custom components 76 | ```jsx 77 | import { QuarkElement, property, customElement } from "quarkc" 78 | import style from "./index.less?inline" 79 | 80 | @customElement({ tag: "my-element", style }) // custom tag/component, CSS 81 | export default class MyElement extends QuarkElement { 82 | @property() // external property 83 | count 84 | 85 | add = () => { 86 | this.count += 1 87 | } 88 | 89 | render() { 90 | return ( 91 | 92 | ) 93 | } 94 | } 95 | ``` 96 | 97 | 3. use 98 | 99 | All kinds of tech stacks work. 100 | ```html 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | ``` 115 | 116 | ### Component packaging 117 | 118 | Package default output is UMD / ESM format 119 | 120 | ```bash 121 | npm run build 122 | ``` 123 | 124 | At this point, the resources under the build product `lib/` can be used directly in the project. (Any front-end project can be used~) 125 | 126 | ```jsx 127 | import "your-element" 128 | 129 | 130 | ``` 131 | 132 | ### Component publishing 133 | 134 | Components can be published to npm, installed with: 135 | 136 | ```bash 137 | npm install your-element 138 | ``` 139 | 140 | Can be used as a CDN 141 | 142 | ```html 143 | 144 | 145 | ``` 146 | 147 | Also available as ES Module (recommended) 148 | ```js 149 | import "your-element" 150 | ``` 151 | 152 | For more details about publishing, click [Publishing](https://quarkc.hellobike.com/#/zh-CN/docs/publishing) 153 | 154 | ### Features 155 | 156 | * **Cross-Technology Stack**: Components can be used in any frame or frameless environment, making your code more reusable 157 | * **The component size is very small and the performance is extremely high**: Because Quarkc uses the browser's native API, your component can achieve optimal performance and small size 158 | * Web Components, Simple, Fast! 159 | * Browser native API, components can be used across technology stacks 160 | * There is no front-end framework Runtime, and the size of Web components is extremely small 161 | * **High performance** design, integration of Shadow DOM and Virtual DOM 162 | * Components are directly decoupled, polished independently, and referenced on demand 163 | 164 | ### Performance reference 165 | 166 | Screenshot of a slightly complex component page running score: 167 | 168 | image 169 | 170 | ### Documentation 171 | 172 | For full documentation, please visit [quarkc.hellobike.com](https://quarkc.hellobike.com) 173 | -------------------------------------------------------------------------------- /demo4gluang/src/gluang.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 链接需要使用全局状态的组件 3 | * 4 | * class MyComponent extends connectStore(QuarkElement){} 5 | * 6 | * connectStore() mixin 会自动使用 stateRecorder 来记录组件使用了哪些 stateVar 变量。 7 | * 就在组件渲染之前,stateRecorder.start() 被调用以开始记录。当 stateVar 被读取时,它的处理程序会将其记录到 stateRecorder。 8 | 当组件完成渲染时,stateRecorder.finish() 方法会被调用。这将停止记录 stateVar 变量,并返回记录的变量。然后观察收集到的 stateVar 变量,当其中一个变量发生变化时,组件就会重新渲染。 9 | 所以当你在 Quarkc 组件上使用 connectStore() mixin 时,所有这些都会为你处理好。 10 | */ 11 | export const connectStore = superclass => { 12 | 13 | class InnerClass extends superclass { 14 | 15 | constructor() { 16 | super(); 17 | this._observers = []; 18 | } 19 | 20 | connectedCallback() { 21 | // 区分 lit(lit 中存在 performUpdate) 22 | if(!this.performUpdate) { 23 | this.update(true); // quarkc 中先去执行 24 | } 25 | } 26 | 27 | // Your framework need this function to init observe state 28 | update(init = false) { 29 | stateRecorder.start(); 30 | 31 | if (!init) { 32 | super.update(); 33 | } else { 34 | super.connectedCallback(); 35 | } 36 | 37 | this._initStateObservers(); 38 | } 39 | 40 | _initStateObservers() { 41 | this._clearStateObservers(); 42 | this._addStateObservers(stateRecorder.finish()); 43 | } 44 | 45 | _addStateObservers(stateVars) { 46 | for (let [state, keys] of stateVars) { 47 | const observer = () => this.requestUpdate(); 48 | this._observers.push([state, observer]); 49 | state.addObserver(observer, keys); 50 | } 51 | } 52 | 53 | _clearStateObservers() { 54 | for (let [state, observer] of this._observers) { 55 | state.removeObserver(observer); 56 | } 57 | this._observers = []; 58 | } 59 | 60 | } 61 | 62 | return InnerClass 63 | } 64 | 65 | // 维护 store 数据,监听变量,无论 get/set 66 | export class createGluang { 67 | private _observers: any; 68 | 69 | constructor() { 70 | this._observers = []; 71 | this._initStateVars(); 72 | } 73 | 74 | /** 75 | * 手动监听 state 76 | * 77 | * myState.addObserver(this.stateObserver) 78 | * myState.addObserver(this.counterObserver, ['counter']) 79 | * @param observer 80 | * @param keys 81 | */ 82 | addObserver(observer, keys) { 83 | this._observers.push({observer, keys}); 84 | } 85 | 86 | /** 87 | * 手动移除监听 88 | * 89 | * myState.removeObserver(this.stateObserver) 90 | * @param observer 91 | */ 92 | removeObserver(observer) { 93 | this._observers = this._observers.filter(observerObj => observerObj.observer !== observer); 94 | } 95 | 96 | _initStateVars() { 97 | if (this.constructor.stateVarOptions) { 98 | for (let [key, options] of Object.entries(this.constructor.stateVarOptions)) { 99 | this._initStateVar(key, options); 100 | } 101 | } 102 | 103 | if (this.constructor.stateVars) { 104 | // State obj's key 105 | for (let [key, value] of Object.entries(this.constructor.stateVars)) { 106 | this._initStateVar(key, {}); 107 | this[key] = value; 108 | } 109 | } 110 | 111 | } 112 | 113 | _initStateVar(key, options) { 114 | // Property already defined, so don't re-define. 115 | if (this.hasOwnProperty(key)) { 116 | return; 117 | } 118 | 119 | options = this._parseOptions(options); 120 | 121 | const stateVar = new options.handler({ 122 | options: options, 123 | recordRead: () => this._recordRead(key), 124 | notifyChange: () => this._notifyChange(key) 125 | }); 126 | 127 | Object.defineProperty( 128 | this, 129 | key, 130 | { 131 | get() { 132 | return stateVar.get(); 133 | }, 134 | set(value) { 135 | if (stateVar.shouldSetValue(value)) { 136 | stateVar.set(value); 137 | } 138 | }, 139 | configurable: true, 140 | enumerable: true 141 | } 142 | ); 143 | 144 | } 145 | 146 | _parseOptions(options) { 147 | 148 | if (!options.handler) { 149 | options.handler = StateVar; 150 | } else { 151 | if (options.propertyMethod && options.propertyMethod.kind === 'method') { 152 | Object.assign(options, options.propertyMethod.descriptor.value.call(this)); 153 | } 154 | } 155 | 156 | return options; 157 | 158 | } 159 | 160 | _recordRead(key) { 161 | stateRecorder.recordRead(this, key); 162 | } 163 | 164 | _notifyChange(key) { 165 | for (const observerObj of this._observers) { 166 | if (!observerObj.keys || observerObj.keys.includes(key)) { 167 | observerObj.observer(key); 168 | } 169 | }; 170 | } 171 | 172 | } 173 | 174 | /** 175 | * 非装饰器声明的 state 176 | * 177 | * static get StateVar() { 178 | return { 179 | counter: 0, 180 | } 181 | } 182 | */ 183 | export class StateVar { 184 | options: Object; 185 | recordRead: any; 186 | notifyChange: any; 187 | value: undefined; 188 | 189 | constructor(args) { 190 | this.options = args.options 191 | this.recordRead = args.recordRead 192 | this.notifyChange = args.notifyChange 193 | this.value = undefined 194 | } 195 | 196 | get() { 197 | this.recordRead() 198 | return this.value 199 | } 200 | 201 | set(value) { 202 | this.value = value; 203 | this.notifyChange(); 204 | } 205 | 206 | shouldSetValue(value) { 207 | return this.value !== value; 208 | } 209 | } 210 | 211 | 212 | export function stateVar(options?: any) { 213 | return element => { 214 | return { 215 | kind: 'field', 216 | key: Symbol(), 217 | placement: 'own', 218 | descriptor: {}, 219 | initializer() { 220 | if (typeof element.initializer === 'function') { 221 | this[element.key] = element.initializer.call(this); 222 | } 223 | }, 224 | finisher(stateClass) { 225 | if (element.kind === 'method') { 226 | options.propertyMethod = element; 227 | } 228 | 229 | if (!stateClass.stateVarOptions) { 230 | stateClass.stateVarOptions = {}; 231 | } 232 | 233 | stateClass.stateVarOptions[element.key] = options; 234 | 235 | } 236 | }; 237 | }; 238 | } 239 | 240 | /** 241 | * connectStore() 的 mixin 使用 stateRecorder 对象来记录在它的上一个渲染周期中哪些 stateVar 变量被访问过。 242 | */ 243 | class StateRecorder { 244 | #log 245 | 246 | constructor() { 247 | this.#log = null; 248 | } 249 | 250 | // Starts the recorder. After calling this, every stateVar variable that is being read, will be recorded, until finish() is called. 251 | start() { 252 | this.#log = new Map(); 253 | } 254 | 255 | recordRead(stateObj, key) { 256 | if (!this.#log) return; 257 | 258 | const keys = this.#log.get(stateObj) || []; 259 | 260 | if (!keys.includes(key)) keys.push(key); 261 | 262 | this.#log.set(stateObj, keys); 263 | } 264 | 265 | finish() { 266 | const stateVars = this.#log; 267 | this.#log = null; 268 | return stateVars; 269 | } 270 | 271 | } 272 | 273 | export const stateRecorder = new StateRecorder(); --------------------------------------------------------------------------------