Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[add] Model, API & Page of Web polyfill CDN #6

Merged
merged 1 commit into from
Mar 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@
- CI / CD: GitHub [Actions][16] + [Vercel][17]
- Monitor service: [Sentry][18]

## Major features

### Open Source license filter

- [introduction](https://kaiyuanshe.feishu.cn/wiki/wikcnRn5pkE3BSvqFUMkJPymaG3)
- [home page](https://oss-toolbox.vercel.app/license-filter/)
- [source code](pages/license-filter.tsx)

## Major examples

1. [Markdown articles](pages/article/)
Expand Down
2 changes: 1 addition & 1 deletion components/MainNavigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const { t } = i18n,
LanguageMenu = dynamic(import('./LanguageMenu'), { ssr: false });

export const MainNavigator: FC = observer(() => (
<Navbar bg="primary" variant="dark" fixed="top" expand="sm" collapseOnSelect>
<Navbar bg="primary" variant="dark" fixed="top" expand="lg" collapseOnSelect>
<Container>
<Navbar.Brand href="/">{t('open_source_treasure_box')}</Navbar.Brand>

Expand Down
4 changes: 2 additions & 2 deletions components/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ export const MainRoutes: () => Link[] = () => [
title: 'GitHub issues',
path: '/issue',
},
{ title: t('license_tool'), path: '/tool/license-filter' },
{ title: t('license_tool'), path: '/license-filter' },
{
title: `${t('Web_polyfill_CDN')} v1`,
path: 'https://polyfill.kaiyuanshe.cn/',
},
{
title: `${t('Web_polyfill_CDN')} v2`,
path: 'https://polyfiller.kaiyuanshe.cn/',
path: '/polyfill',
},
{ title: t('open_source_mirror'), path: 'http://mirror.kaiyuanshe.cn/' },
{ title: 'Git Pager', path: 'https://git-pager.vercel.app/' },
Expand Down
24 changes: 2 additions & 22 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
version: '3'

networks:
idea2app:

services:
oss-toolbox:
image: kaiyuanshe/oss-toolbox
networks:
- idea2app
ports:
- 3002:3000
healthcheck:
test: ['CMD-SHELL', 'curl -f http://localhost:3000/ || exit 1']
interval: 3s
Expand All @@ -25,20 +22,3 @@ services:
driver: 'json-file'
options:
max-size: '10m'

autoheal:
image: willfarrell/autoheal:1.2.0
volumes:
- /var/run/docker.sock:/var/run/docker.sock
restart: always

caddy:
depends_on:
- oss-toolbox
image: caddy
ports:
- 80:80
- 443:443
networks:
- idea2app
command: caddy reverse-proxy --from your.domain.com --to oss-toolbox:3000
88 changes: 88 additions & 0 deletions models/Polyfill.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { observable } from 'mobx';
import { BaseModel, toggle } from 'mobx-restful';
import { buildURLData } from 'web-utility';

import { PolyfillHost } from '../pages/api/polyfill';
import { ownClient } from './Base';

export type JSEnvironment = 'window' | 'worker' | 'node';

export type LibraryAlias = Record<JSEnvironment, string>;

export type LibraryPath = Record<JSEnvironment, string[]>;

export interface Library {
features: string[];
dependencies: string[];
contexts: {};
meta?: object;
mustComeAfter?: string | string[];
}

export interface RemoteLibrary extends Library {
library: string | LibraryAlias;
relativePaths: string[] | LibraryPath;
}

export interface LocalLibrary extends Library {
version: string;
localPaths: string[];
}

export interface Alias {
polyfills: string[];
}

export type PolyfillIndex = Record<
string,
RemoteLibrary | LocalLibrary | Alias
>;

export const UserAgent: Record<string, string> = {
'IE 11':
'Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko',
'Edge 18':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.19042',
'Safari 13.2':
'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1',
'Opera 9.8': 'Opera/9.80 (Windows NT 6.1) Presto/2.12.388 Version/12.16',
'UC 12':
'Mozilla/5.0 (Linux; U; Android 8.1.0; en-US; Nexus 6P Build/OPM7.181205.001) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/57.0.2987.108 UCBrowser/12.11.1.1197 Mobile Safari/537.36',
'Firefox 70':
'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:70.0) Gecko/20100101 Firefox/70.0',
'Android 4':
'Mozilla/5.0 (Linux; U; Android 4.0.2; en-us; Galaxy Nexus Build/ICL53F) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30',
};

export class PolyfillModel extends BaseModel {
@observable
accessor index: PolyfillIndex = {};

@observable
accessor currentUA = '';

@observable
accessor sourceCode = '';

@toggle('downloading')
async getIndex() {
const { body } = await ownClient.get<PolyfillIndex>(
`${PolyfillHost}/index.json`,
);
return (this.index = body!);
}

@toggle('downloading')
async getSourceCode(UA: string, features: string[]) {
const response = await fetch(
`/api/polyfill?${buildURLData({ features })}`,
{
headers: { 'User-Agent': UserAgent[UA] },
},
);
this.currentUA = UA;
return (this.sourceCode = await response.text());
}
}

export default new PolyfillModel();
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"next-pwa": "~5.6.0",
"next-ssr-middleware": "^0.7.0",
"next-with-less": "^3.0.1",
"primereact": "^10.5.1",
"prismjs": "^1.29.0",
"react": "^18.2.0",
"react-bootstrap": "^2.10.1",
Expand Down
27 changes: 27 additions & 0 deletions pages/api/polyfill.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { HTTPClient } from 'koajax';

import { safeAPI } from './core';

export const PolyfillHost = 'https://polyfiller.kaiyuanshe.cn';

export const polyfillClient = new HTTPClient({
baseURI: PolyfillHost,
responseType: 'text',
});

export default safeAPI(async ({ method, url, headers, body }, response) => {
delete headers.host;

const { status, body: data } = await polyfillClient.request({
// @ts-ignore
method,
path: url!,
// @ts-ignore
headers,
body: body || undefined,
});

response.status(status);
response.setHeader('Access-Control-Allow-Headers', '*');
response.send(data);
});
114 changes: 114 additions & 0 deletions pages/polyfill.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { Loading } from 'idea-react';
import { computed, observable } from 'mobx';
import { textJoin } from 'mobx-i18n';
import { observer } from 'mobx-react';
import { compose, translator } from 'next-ssr-middleware';
import { TreeNode } from 'primereact/treenode';
import { TreeSelect, TreeSelectSelectionKeysType } from 'primereact/treeselect';
import { Component } from 'react';
import { Card, Container, Dropdown, DropdownButton } from 'react-bootstrap';

import { PageHead } from '../components/PageHead';
import polyfillStore, { UserAgent } from '../models/Polyfill';
import { i18n } from '../models/Translation';
import { PolyfillHost } from './api/polyfill';

export const getServerSideProps = compose(translator(i18n));

const { t } = i18n;

@observer
export default class PolyfillPage extends Component {
@computed
get options() {
return Object.entries(polyfillStore.index)
.map(([key, data]) => !('polyfills' in data) && { key, label: key, data })
.filter(Boolean) as TreeNode[];
}

@observable
accessor selectOptions: TreeSelectSelectionKeysType = {};

@computed
get features() {
return Object.keys(this.selectOptions);
}

@computed
get polyfillURL() {
return `${PolyfillHost}/api/polyfill?features=${this.features}`;
}

componentDidMount() {
polyfillStore.getIndex();
}

renderContent() {
const { options, selectOptions, features, polyfillURL } = this,
{ currentUA, sourceCode } = polyfillStore;

return (
<main className="d-flex flex-column gap-3 mb-3">
<TreeSelect
placeholder="features"
display="chip"
filter
selectionMode="checkbox"
options={options}
value={selectOptions}
onChange={({ value }) =>
(this.selectOptions = value as TreeSelectSelectionKeysType)
}
/>
<div className="d-flex justify-content-around align-items-center">
<DropdownButton title={textJoin(t('examples'), currentUA)}>
{Object.entries(UserAgent).map(([name, value]) => (
<Dropdown.Item
key={name}
title={value}
onClick={() => polyfillStore.getSourceCode(name, features)}
>
{name}
</Dropdown.Item>
))}
</DropdownButton>

<h1>{t('Web_polyfill_CDN')}</h1>
</div>

<Card body>
<a target="_blank" href={polyfillURL} rel="noreferrer">
{polyfillURL}
</a>
<hr />
<pre>
<code>{sourceCode}</code>
</pre>
</Card>
</main>
);
}

render() {
const { downloading } = polyfillStore;

return (
<Container>
<PageHead title={t('Web_polyfill_CDN')}>
<link
rel="stylesheet"
href="https://unpkg.com/primereact/resources/primereact.min.css"
/>
<link
rel="stylesheet"
href="https://unpkg.com/primereact/resources/themes/bootstrap4-light-blue/theme.css"
/>
</PageHead>

{downloading > 0 && <Loading />}

{this.renderContent()}
</Container>
);
}
}
21 changes: 21 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading