├── .editorconfig
├── .github
├── FUNDING.yml
└── workflows
│ └── ci.yml
├── .gitignore
├── build
├── index.js
├── package.json
└── util.js
├── bump.json
├── examples
├── preact.esbuild
│ ├── package.json
│ └── src
│ │ ├── index.html
│ │ ├── index.js
│ │ ├── routes
│ │ ├── _layout.jsx
│ │ ├── _layout.less
│ │ ├── _layout.scss
│ │ ├── _layout.styl
│ │ ├── blog
│ │ │ ├── [id].jsx
│ │ │ └── index.jsx
│ │ ├── index.jsx
│ │ ├── index.less
│ │ ├── index.sass
│ │ └── index.styl
│ │ └── static
│ │ └── robots.txt
├── preact
│ ├── package.json
│ └── src
│ │ ├── index.html
│ │ ├── index.js
│ │ ├── routes
│ │ ├── _layout.jsx
│ │ ├── blog
│ │ │ ├── [id].jsx
│ │ │ └── index.jsx
│ │ └── index.jsx
│ │ └── static
│ │ └── robots.txt
├── svelte.typescript
│ ├── freshie.config.js
│ ├── package.json
│ ├── src
│ │ ├── errors
│ │ │ ├── 5xx.svelte
│ │ │ └── _layout.svelte
│ │ ├── index.dom.ts
│ │ ├── index.html
│ │ ├── public
│ │ │ ├── manifest.json
│ │ │ └── robots.txt
│ │ └── routes
│ │ │ ├── _layout.svelte
│ │ │ ├── blog
│ │ │ ├── [id].svelte
│ │ │ └── index.svelte
│ │ │ └── index.svelte
│ └── tsconfig.json
├── svelte
│ ├── package.json
│ └── src
│ │ ├── errors
│ │ ├── 5xx.svelte
│ │ └── _layout.svelte
│ │ ├── index.dom.js
│ │ ├── index.html
│ │ ├── public
│ │ ├── manifest.json
│ │ └── robots.txt
│ │ └── routes
│ │ ├── _layout.svelte
│ │ ├── blog
│ │ ├── [id].svelte
│ │ └── index.svelte
│ │ └── index.svelte
└── vue
│ ├── freshie.config.js
│ ├── package.json
│ └── src
│ ├── index.js
│ └── routes
│ ├── blog
│ ├── [id].vue
│ └── index.vue
│ └── index.vue
├── license
├── package.json
├── packages
├── @freshie
│ ├── plugin.babel
│ │ ├── config.js
│ │ ├── package.json
│ │ └── readme.md
│ ├── plugin.esbuild
│ │ ├── config.js
│ │ ├── package.json
│ │ └── readme.md
│ ├── plugin.postcss
│ │ ├── assets.js
│ │ ├── config.js
│ │ ├── index.js
│ │ ├── package.json
│ │ ├── readme.md
│ │ └── runtime.js
│ ├── plugin.typescript
│ │ ├── config.js
│ │ ├── package.json
│ │ └── readme.md
│ ├── ssr.node
│ │ ├── config.js
│ │ ├── entry.js
│ │ ├── index.d.ts
│ │ ├── index.js
│ │ └── package.json
│ ├── ssr.worker
│ │ ├── config.js
│ │ ├── entry.js
│ │ ├── index.js
│ │ └── package.json
│ ├── ui.preact
│ │ ├── _error.jsx
│ │ ├── config.js
│ │ ├── index.d.ts
│ │ ├── index.js
│ │ ├── package.json
│ │ └── readme.md
│ ├── ui.svelte
│ │ ├── _error.svelte
│ │ ├── config.js
│ │ ├── index.d.ts
│ │ ├── index.js
│ │ ├── package.json
│ │ └── readme.md
│ └── ui.vue
│ │ ├── config.js
│ │ ├── index.js
│ │ └── package.json
└── freshie
│ ├── @types
│ ├── index.d.ts
│ └── rollup.d.ts
│ ├── bin.js
│ ├── env
│ ├── index.d.ts
│ ├── index.js
│ └── index.mjs
│ ├── http
│ ├── index.d.ts
│ ├── index.js
│ └── index.mjs
│ ├── index.d.ts
│ ├── package.json
│ ├── router
│ ├── index.d.ts
│ ├── index.js
│ └── index.mjs
│ ├── runtime
│ ├── index.d.ts
│ └── index.dom.js
│ └── src
│ ├── commands
│ ├── build.ts
│ └── watch
│ │ ├── index.ts
│ │ └── watcher.ts
│ ├── config
│ ├── index.ts
│ ├── options.ts
│ └── plugins
│ │ ├── copy.ts
│ │ ├── html.ts
│ │ ├── index.ts
│ │ ├── router.ts
│ │ ├── runtime.ts
│ │ ├── summary.ts
│ │ ├── template.ts
│ │ └── terser.ts
│ ├── index.ts
│ └── utils
│ ├── __tests__
│ ├── argv.ts
│ ├── errors.ts
│ ├── fs.ts
│ └── routes.ts
│ ├── argv.ts
│ ├── assert.ts
│ ├── entries.ts
│ ├── errors.ts
│ ├── fs.ts
│ ├── index.ts
│ ├── log.ts
│ ├── pretty.ts
│ ├── routes.ts
│ └── scoped.ts
├── pnpm-workspace.yaml
├── readme.md
└── tsconfig.json
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_size = 2
6 | indent_style = tab
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | [*.{json,yml,md}]
13 | indent_style = space
14 |
15 | [fixtures/**.babel.js]
16 | insert_final_newline = false
17 | indent_style = space
18 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: lukeed
2 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on: push
4 |
5 | jobs:
6 | test:
7 | name: Node.js v${{ matrix.nodejs }}
8 | runs-on: ubuntu-latest
9 | strategy:
10 | matrix:
11 | nodejs: [10, 14, 18]
12 | steps:
13 | - uses: actions/checkout@v3
14 | - uses: actions/setup-node@v3
15 | with:
16 | node-version: ${{ matrix.nodejs }}
17 | - uses: pnpm/action-setup@v2.2.4
18 | with:
19 | version: 5
20 | run_install: false
21 |
22 | - name: (env) cache
23 | uses: actions/cache@master
24 | with:
25 | path: |
26 | node_modules
27 | */*/node_modules
28 | key: ${{ runner.os }}-${{ hashFiles('**/package.json') }}
29 |
30 | - name: Install
31 | run: pnpm install
32 |
33 | - name: Test
34 | if: matrix.nodejs < 18
35 | run: pnpm test
36 |
37 | - name: Test w/ Coverage
38 | if: matrix.nodejs >= 18
39 | run: |
40 | pnpm add -g c8
41 | c8 --include=packages pnpm test
42 |
43 | # - name: Report
44 | # if: matrix.nodejs >= 18
45 | # run: |
46 | # c8 report --reporter=text-lcov > coverage.lcov
47 | # bash <(curl -s https://codecov.io/bash)
48 | # env:
49 | # CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
50 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | *-lock.*
4 | *.lock
5 | *.log
6 |
7 | examples/**/build/**
8 | packages/**/build/**
9 |
--------------------------------------------------------------------------------
/build/index.js:
--------------------------------------------------------------------------------
1 | const { transpileModule } = require('typescript');
2 | const tsconfig = require('../tsconfig.json');
3 | const { build } = require('./util');
4 |
5 | const terser = require('rollup-plugin-terser').terser();
6 | const resolve = require('@rollup/plugin-node-resolve').default({
7 | extensions: ['.ts', '.mjs', '.js', '.json']
8 | });
9 |
10 | const typescript = {
11 | name: 'typescript',
12 | transform(code, file) {
13 | if (!/\.ts$/.test(file)) return code;
14 |
15 | // @ts-ignore
16 | let output = transpileModule(code, {
17 | ...tsconfig,
18 | fileName: file
19 | });
20 |
21 | return {
22 | code: output.outputText,
23 | map: output.sourceMapText || null
24 | };
25 | }
26 | };
27 |
28 | // ---
29 |
30 | build('freshie', {
31 | input: 'packages/freshie/src/index.ts',
32 | interop: false,
33 | format: {
34 | cjs: 'build/index.js'
35 | },
36 | plugins: [
37 | resolve,
38 | typescript,
39 | terser
40 | ],
41 | });
42 |
--------------------------------------------------------------------------------
/build/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "devDependencies": {
4 | "@rollup/plugin-node-resolve": "9.0.0",
5 | "escalade": "3.1.0",
6 | "kleur": "4.1.1",
7 | "rollup": "2.27.1",
8 | "rollup-plugin-terser": "7.0.2",
9 | "rollup-plugin-typescript2": "0.27.2"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/build/util.js:
--------------------------------------------------------------------------------
1 | // NOTE: lukeed/bundt fork
2 | const kleur = require('kleur');
3 | const { join } = require('path');
4 | const { gzipSync } = require('zlib');
5 | const { builtinModules } = require('module');
6 | const { rollup } = require('rollup');
7 | const scale = require('escalade');
8 |
9 | const GUTTER = ' '; // 4
10 | const UNITS = ['B ', 'kB', 'MB', 'GB'];
11 | const lpad = (str, max, fmt) => fmt(' '.repeat(max - str.length) + str);
12 | const rpad = (str, max, fmt) => fmt(str + ' '.repeat(max - str.length));
13 | const COL1 = kleur.dim().bold().italic().underline;
14 | const COL2 = kleur.dim().bold().italic;
15 | const PKG = 'package.json';
16 |
17 | function size(val=0) {
18 | if (val < 1e3) return `${val} ${UNITS[0]}`;
19 | let exp = Math.min(Math.floor(Math.log10(val) / 3), UNITS.length - 1) || 1;
20 | let out = (val / Math.pow(1e3, exp)).toPrecision(3);
21 | let idx = out.indexOf('.');
22 | if (idx === -1) out += '.00';
23 | else if (out.length - idx - 1 !== 2) {
24 | out = (out + '00').substring(0, idx + 3); // 2 + 1 for 0-based
25 | }
26 | return out + ' ' + UNITS[exp];
27 | }
28 |
29 | function table(mode, arr) {
30 | let label = `Package: ${mode}`;
31 | let f=label.length, s=8, g=6, out='';
32 |
33 | if (arr.length === 1) {
34 | f = Math.max(f, arr[0].file.length);
35 | g = Math.max(g, arr[0].gzip.length);
36 | s = Math.max(s, arr[0].size.length);
37 | } else {
38 | arr.sort((a, b) => {
39 | f = Math.max(f, a.file.length, b.file.length);
40 | g = Math.max(g, a.gzip.length, b.gzip.length);
41 | s = Math.max(s, a.size.length, b.size.length);
42 | return a.file.length - b.file.length;
43 | });
44 | }
45 |
46 | f += 4; // spacing
47 |
48 | out += rpad(label, f, COL1) + GUTTER + lpad('Filesize', s, COL1) + ' ' + lpad('(gzip)', g, COL2) + '\n';
49 |
50 | arr.forEach(obj => {
51 | out += rpad(obj.file, f, kleur.white) + GUTTER + lpad(obj.size, s, kleur.cyan) + ' ' + lpad(obj.gzip, g, kleur.dim().italic) + '\n';
52 | });
53 |
54 | console.log('\n' + out);
55 | }
56 |
57 | exports.build = async function (name, opts = {}) {
58 | try {
59 | const FILES = []; // outputs
60 | const { input, format, externals=[], plugins=[], ...rest } = opts;
61 | const external = [...builtinModules, ...externals];
62 | const pkgDir = join('packages', name);
63 |
64 | for (let key in format) {
65 | format[key] = join(pkgDir, format[key]);
66 | }
67 |
68 | // @ts-ignore - bad commonjs definition
69 | const file = await scale(input, (dir, files) => {
70 | return files.includes(PKG) && join(dir, PKG);
71 | });
72 |
73 | const pkg = file && require(file);
74 |
75 | if (pkg) external.push(
76 | ...Object.keys(pkg.dependencies || {}),
77 | ...Object.keys(pkg.peerDependencies || {}),
78 | ...Object.keys(pkg.optionalDependencies || {}),
79 | );
80 |
81 | const bundle = await rollup({ input, plugins, external });
82 |
83 | await Promise.all(
84 | Object.keys(format).map(key => {
85 | return bundle.write({
86 | // @ts-ignore
87 | format: key,
88 | strict: false,
89 | esModule: false,
90 | file: format[key],
91 | ...rest,
92 | }).then(result => {
93 | let { code } = result.output[0];
94 | FILES.push({
95 | file: format[key].replace(/^(\.[\\\/])?/, ''),
96 | gzip: size(gzipSync(code).length),
97 | size: size(code.length),
98 | });
99 | });
100 | })
101 | );
102 |
103 | table(name, FILES);
104 | } catch (err) {
105 | let msg = (err.message || err || 'Unknown error').replace(/(\r?\n)/g, '$1 ');
106 | console.error(kleur.red().bold('[BUILD] ') + msg);
107 | process.exit(1);
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/bump.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.0.8",
3 | "packages": [
4 | "packages"
5 | ],
6 | "message": "v{VERSION}"
7 | }
--------------------------------------------------------------------------------
/examples/preact.esbuild/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "build": "freshie build"
5 | },
6 | "dependencies": {
7 | "preact": "10.4.8"
8 | },
9 | "devDependencies": {
10 | "@freshie/plugin.esbuild": "workspace:*",
11 | "@freshie/plugin.postcss": "workspace:*",
12 | "@freshie/ui.preact": "workspace:*",
13 | "esbuild": "0.7.7",
14 | "freshie": "workspace:*",
15 | "postcss": "7.0.32",
16 | "cssnano": "4.1.10",
17 | "stylus": "0.54.8"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/examples/preact.esbuild/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | preact | freshie
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/examples/preact.esbuild/src/index.js:
--------------------------------------------------------------------------------
1 | import * as freshie from 'freshie/runtime';
2 | import * as preact from '@freshie/ui.preact';
3 |
4 | freshie.start({
5 | base: '/',
6 | // target: document.body,
7 | hydrate: preact.hydrate,
8 | render: preact.render,
9 | });
10 |
--------------------------------------------------------------------------------
/examples/preact.esbuild/src/routes/_layout.jsx:
--------------------------------------------------------------------------------
1 | import { h } from 'preact';
2 | import * as styles from './_layout.styl';
3 |
4 | export default function Layout(props) {
5 | return (
6 |
7 | {props.children}
8 |
9 | );
10 | }
11 |
--------------------------------------------------------------------------------
/examples/preact.esbuild/src/routes/_layout.less:
--------------------------------------------------------------------------------
1 | .layout {
2 | border: 1px solid red;
3 | }
4 |
--------------------------------------------------------------------------------
/examples/preact.esbuild/src/routes/_layout.scss:
--------------------------------------------------------------------------------
1 | .layout {
2 | border: 1px solid red;
3 | }
4 |
--------------------------------------------------------------------------------
/examples/preact.esbuild/src/routes/_layout.styl:
--------------------------------------------------------------------------------
1 | .layout
2 | border 1px solid red
3 |
--------------------------------------------------------------------------------
/examples/preact.esbuild/src/routes/blog/[id].jsx:
--------------------------------------------------------------------------------
1 | import { get } from 'freshie/http';
2 | import { h, Fragment } from 'preact';
3 |
4 | export async function preload(req) {
5 | let res = await get(`https://jsonplaceholder.typicode.com/posts/${req.params.id}`);
6 | return { article: res.data };
7 | }
8 |
9 | export default function Article(props) {
10 | // {/*
11 | // {article.title}
12 | // */}
13 | return (
14 | <>
15 | { props.article.title }
16 |
17 | >
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/examples/preact.esbuild/src/routes/blog/index.jsx:
--------------------------------------------------------------------------------
1 | import { get } from 'freshie/http';
2 | import { h, Fragment } from 'preact';
3 |
4 | export async function preload() {
5 | let res = await get('https://jsonplaceholder.typicode.com/posts');
6 | return { articles: res.data };
7 | }
8 |
9 | export default function Blog(props) {
10 | //
11 | // Articles
12 | //
13 |
14 | return (
15 | <>
16 | All Articles
17 |
18 |
19 |
20 | {
21 | props.articles.map(obj => (
22 | -
23 | {obj.title}
24 |
25 | ))
26 | }
27 |
28 |
29 | >
30 | );
31 | }
32 |
--------------------------------------------------------------------------------
/examples/preact.esbuild/src/routes/index.jsx:
--------------------------------------------------------------------------------
1 | import { h, Fragment } from 'preact';
2 | import * as styles from './index.styl';
3 |
4 | export default function Home(props) {
5 | const name = props.name || 'world';
6 |
7 | return (
8 | <>
9 | Hello {name}
10 | Blog
11 | >
12 | )
13 | }
14 |
--------------------------------------------------------------------------------
/examples/preact.esbuild/src/routes/index.less:
--------------------------------------------------------------------------------
1 | .title {
2 | color: red;
3 | }
4 |
--------------------------------------------------------------------------------
/examples/preact.esbuild/src/routes/index.sass:
--------------------------------------------------------------------------------
1 | .title
2 | color: red
3 |
--------------------------------------------------------------------------------
/examples/preact.esbuild/src/routes/index.styl:
--------------------------------------------------------------------------------
1 | .title
2 | color red
3 |
--------------------------------------------------------------------------------
/examples/preact.esbuild/src/static/robots.txt:
--------------------------------------------------------------------------------
1 | robots.txt
2 |
--------------------------------------------------------------------------------
/examples/preact/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "build": "freshie build"
5 | },
6 | "dependencies": {
7 | "preact": "10.4.8"
8 | },
9 | "devDependencies": {
10 | "@babel/core": "7.11.6",
11 | "@babel/plugin-transform-react-jsx": "7.10.4",
12 | "@freshie/plugin.babel": "workspace:*",
13 | "@freshie/ui.preact": "workspace:*",
14 | "freshie": "workspace:*"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/examples/preact/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | preact | freshie
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/examples/preact/src/index.js:
--------------------------------------------------------------------------------
1 | import * as freshie from 'freshie/runtime';
2 | import * as preact from '@freshie/ui.preact';
3 |
4 | freshie.start({
5 | base: '/',
6 | // target: document.body,
7 | hydrate: preact.hydrate,
8 | render: preact.render,
9 | });
10 |
--------------------------------------------------------------------------------
/examples/preact/src/routes/_layout.jsx:
--------------------------------------------------------------------------------
1 | import { h } from 'preact';
2 |
3 | export default function Layout(props) {
4 | return (
5 |
6 | { props.children }
7 |
8 | )
9 | }
10 |
--------------------------------------------------------------------------------
/examples/preact/src/routes/blog/[id].jsx:
--------------------------------------------------------------------------------
1 | import { get } from 'freshie/http';
2 | import { h, Fragment } from 'preact';
3 |
4 | // export async function preload(req) {
5 | // let res = await fetch(`https://jsonplaceholder.typicode.com/posts/${req.params.id}`);
6 | // let article = await res.json();
7 | // return { article };
8 | // }
9 |
10 | export async function preload(req) {
11 | let res = await get(`https://jsonplaceholder.typicode.com/posts/${req.params.id}`);
12 | return { article: res.data };
13 | }
14 |
15 | export default function Article(props) {
16 | // {/*
17 | // {article.title}
18 | // */}
19 | return (
20 | <>
21 | { props.article.title }
22 |
23 | >
24 | )
25 | }
26 |
--------------------------------------------------------------------------------
/examples/preact/src/routes/blog/index.jsx:
--------------------------------------------------------------------------------
1 | import { get } from 'freshie/http';
2 | import { h, Fragment } from 'preact';
3 |
4 | // export async function preload() {
5 | // let res = await fetch(`https://jsonplaceholder.typicode.com/posts`);
6 | // let articles = await res.json();
7 | // return { articles };
8 | // }
9 |
10 | export async function preload() {
11 | let res = await get('https://jsonplaceholder.typicode.com/posts');
12 | return { articles: res.data };
13 | }
14 |
15 | export default function Blog(props) {
16 | //
17 | // Articles
18 | //
19 |
20 | return (
21 | <>
22 | All Articles
23 |
24 |
25 |
26 | {
27 | props.articles.map(obj => (
28 | -
29 | {obj.title}
30 |
31 | ))
32 | }
33 |
34 |
35 | >
36 | )
37 | }
38 |
--------------------------------------------------------------------------------
/examples/preact/src/routes/index.jsx:
--------------------------------------------------------------------------------
1 | import { h, Fragment } from 'preact';
2 |
3 | export default function Home(props) {
4 | const name = props.name || 'world';
5 |
6 | return (
7 | <>
8 | Hello {name}
9 | Blog
10 | >
11 | )
12 | }
13 |
--------------------------------------------------------------------------------
/examples/preact/src/static/robots.txt:
--------------------------------------------------------------------------------
1 | robots.txt
2 |
--------------------------------------------------------------------------------
/examples/svelte.typescript/freshie.config.js:
--------------------------------------------------------------------------------
1 | // TODO? include inside ui.svelte
2 | exports.svelte = function (config) {
3 | // @ts-ignore - export default type
4 | config.preprocess = require('svelte-preprocess')()
5 | }
6 |
--------------------------------------------------------------------------------
/examples/svelte.typescript/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "build": "freshie build",
5 | "watch": "freshie watch"
6 | },
7 | "devDependencies": {
8 | "@freshie/plugin.postcss": "workspace:*",
9 | "@freshie/plugin.typescript": "workspace:*",
10 | "@freshie/ssr.node": "workspace:*",
11 | "@freshie/ui.svelte": "workspace:*",
12 | "freshie": "workspace:*",
13 | "postcss": "7.0.32",
14 | "svelte": "3.29.0",
15 | "svelte-preprocess": "4.3.1",
16 | "typescript": "4.0.3"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/examples/svelte.typescript/src/errors/5xx.svelte:
--------------------------------------------------------------------------------
1 |
8 |
9 |
12 |
13 | Error: {status}
14 | this is a custom 5xx error page
15 |
--------------------------------------------------------------------------------
/examples/svelte.typescript/src/errors/_layout.svelte:
--------------------------------------------------------------------------------
1 |
2 | i am a layout
3 |
4 |
5 |
6 |
11 |
--------------------------------------------------------------------------------
/examples/svelte.typescript/src/index.dom.ts:
--------------------------------------------------------------------------------
1 | import * as freshie from 'freshie/runtime';
2 | import * as svelte from '@freshie/ui.svelte';
3 |
4 | freshie.start({
5 | hydrate: svelte.hydrate,
6 | render: svelte.render,
7 | });
8 |
--------------------------------------------------------------------------------
/examples/svelte.typescript/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | svelte | freshie
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/examples/svelte.typescript/src/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "freshie",
3 | "name": "freshie + svelte",
4 | "background_color": "#3E82F7",
5 | "orientation": "portrait",
6 | "theme_color": "#1e88e5",
7 | "display": "standalone",
8 | "start_url": "/",
9 | "icons": [
10 | {
11 | "src": "/icon.png",
12 | "type": "image/png",
13 | "sizes": "512x512"
14 | },
15 | {
16 | "src": "/icon@2x.png",
17 | "type": "image/png",
18 | "sizes": "1024x1024"
19 | }
20 | ]
21 | }
22 |
--------------------------------------------------------------------------------
/examples/svelte.typescript/src/public/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Disallow:
3 |
--------------------------------------------------------------------------------
/examples/svelte.typescript/src/routes/_layout.svelte:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
--------------------------------------------------------------------------------
/examples/svelte.typescript/src/routes/blog/[id].svelte:
--------------------------------------------------------------------------------
1 |
10 |
11 |
14 |
15 |
16 | {article.title}
17 |
18 |
19 | {article.title}
20 |
21 |
22 | {@html article.body}
23 |
24 |
--------------------------------------------------------------------------------
/examples/svelte.typescript/src/routes/blog/index.svelte:
--------------------------------------------------------------------------------
1 |
12 |
13 |
16 |
17 |
18 | Articles
19 |
20 |
21 | All Articles
22 |
23 |
24 |
25 | {#each articles as article (article.id)}
26 | - {article.title}
27 | {:else}
28 | - NO POSTS
29 | {/each}
30 |
31 |
32 |
--------------------------------------------------------------------------------
/examples/svelte.typescript/src/routes/index.svelte:
--------------------------------------------------------------------------------
1 |
4 |
5 | Hello {name}
6 |
7 | Blog
8 |
9 |
14 |
--------------------------------------------------------------------------------
/examples/svelte.typescript/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2017",
4 | "module": "esnext",
5 | "noImplicitAny": true,
6 | "forceConsistentCasingInFileNames": true,
7 | "allowSyntheticDefaultImports": true,
8 | "moduleResolution": "node",
9 | "removeComments": true,
10 | "allowJs": true,
11 | "noEmit": true
12 | },
13 | "include": [
14 | "src"
15 | ],
16 | "exclude": [
17 | "node_modules",
18 | "build"
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/examples/svelte/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "build": "freshie build",
5 | "watch": "freshie watch"
6 | },
7 | "devDependencies": {
8 | "@freshie/ssr.node": "workspace:*",
9 | "@freshie/ui.svelte": "workspace:*",
10 | "freshie": "workspace:*",
11 | "svelte": "3.29.0"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/examples/svelte/src/errors/5xx.svelte:
--------------------------------------------------------------------------------
1 |
6 |
7 |
10 |
11 | Error: {status}
12 | this is a custom 5xx error page
13 |
--------------------------------------------------------------------------------
/examples/svelte/src/errors/_layout.svelte:
--------------------------------------------------------------------------------
1 |
2 | i am a layout
3 |
4 |
5 |
6 |
11 |
--------------------------------------------------------------------------------
/examples/svelte/src/index.dom.js:
--------------------------------------------------------------------------------
1 | import * as freshie from 'freshie/runtime';
2 | import * as svelte from '@freshie/ui.svelte';
3 |
4 | freshie.start({
5 | base: '/',
6 | // target: document.body,
7 | hydrate: svelte.hydrate,
8 | render: svelte.render,
9 | });
10 |
--------------------------------------------------------------------------------
/examples/svelte/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | svelte | freshie
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/examples/svelte/src/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "freshie",
3 | "name": "freshie + svelte",
4 | "background_color": "#3E82F7",
5 | "orientation": "portrait",
6 | "theme_color": "#1e88e5",
7 | "display": "standalone",
8 | "start_url": "/",
9 | "icons": [
10 | {
11 | "src": "/icon.png",
12 | "type": "image/png",
13 | "sizes": "512x512"
14 | },
15 | {
16 | "src": "/icon@2x.png",
17 | "type": "image/png",
18 | "sizes": "1024x1024"
19 | }
20 | ]
21 | }
22 |
--------------------------------------------------------------------------------
/examples/svelte/src/public/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Disallow:
3 |
--------------------------------------------------------------------------------
/examples/svelte/src/routes/_layout.svelte:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
--------------------------------------------------------------------------------
/examples/svelte/src/routes/blog/[id].svelte:
--------------------------------------------------------------------------------
1 |
14 |
15 |
18 |
19 |
20 | {article.title}
21 |
22 |
23 | {article.title}
24 |
25 |
26 | {@html article.body}
27 |
28 |
--------------------------------------------------------------------------------
/examples/svelte/src/routes/blog/index.svelte:
--------------------------------------------------------------------------------
1 |
14 |
15 |
18 |
19 |
20 | Articles
21 |
22 |
23 | All Articles
24 |
25 |
26 |
27 | {#each articles as article (article.id)}
28 | - {article.title}
29 | {:else}
30 | - NO POSTS
31 | {/each}
32 |
33 |
34 |
--------------------------------------------------------------------------------
/examples/svelte/src/routes/index.svelte:
--------------------------------------------------------------------------------
1 |
4 |
5 | Hello {name}
6 |
7 | Blog
8 |
9 |
14 |
--------------------------------------------------------------------------------
/examples/vue/freshie.config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * NOTE: WIP -- no SSR attempts yet
3 | */
4 |
5 | exports.rollup = function (config) {
6 | config.plugins.push(
7 | // https://rollup-plugin-vue.vuejs.org/options.html
8 | require('rollup-plugin-vue')({
9 | css: false,
10 | })
11 | )
12 | }
13 |
--------------------------------------------------------------------------------
/examples/vue/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "build": "freshie build"
5 | },
6 | "dependencies": {
7 | "vue": "2.6.12"
8 | },
9 | "devDependencies": {
10 | "@freshie/ui.vue": "workspace:*",
11 | "rollup-plugin-vue": "5.1.9",
12 | "freshie": "workspace:*",
13 | "vue-template-compiler": "2.6.12"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/examples/vue/src/index.js:
--------------------------------------------------------------------------------
1 | import * as freshie from 'freshie/runtime';
2 | import * as vue from '@freshie/ui.vue';
3 |
4 | freshie.start({
5 | base: '/',
6 | target: document.body,
7 | hydrate: vue.hydrate,
8 | render: vue.render,
9 | });
10 |
--------------------------------------------------------------------------------
/examples/vue/src/routes/blog/[id].vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ article.title }}
4 |
5 |
6 |
7 |
8 |
29 |
--------------------------------------------------------------------------------
/examples/vue/src/routes/blog/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
All Articles
4 |
5 |
12 |
13 |
14 |
15 |
35 |
--------------------------------------------------------------------------------
/examples/vue/src/routes/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Hello {{ name }}
4 |
5 |
Blog
6 |
7 |
8 |
9 |
18 |
--------------------------------------------------------------------------------
/license:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) Luke Edwards (https://lukeed.com)
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
13 | all 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
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "version": "0.0.2",
4 | "repository": "lukeed/freshie",
5 | "license": "MIT",
6 | "scripts": {
7 | "build": "node build",
8 | "pretest": "tsc --noEmit --skipLibCheck",
9 | "test": "uvu -r ts-node/register packages __tests__ -i fixtures"
10 | },
11 | "engines": {
12 | "node": ">=10"
13 | },
14 | "devDependencies": {
15 | "ts-node": "10.2.1",
16 | "typescript": "4.4.3",
17 | "uvu": "0.5.1"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/packages/@freshie/plugin.babel/config.js:
--------------------------------------------------------------------------------
1 | const toArray = (x, y=[]) => x == null ? y : y.concat(x);
2 |
3 | exports.babel = function (config) {
4 | config.babelrc = false;
5 | config.babelHelpers = 'bundled';
6 | config.presets = toArray(config.presets);
7 | config.exclude = toArray(config.exclude, ['node_modules/**']);
8 | config.plugins = toArray(config.plugins);
9 | }
10 |
11 | exports.rollup = function (config, context, options) {
12 | config.plugins.push(
13 | require('@rollup/plugin-babel').default(options.babel)
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/packages/@freshie/plugin.babel/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.0.3",
3 | "name": "@freshie/plugin.babel",
4 | "dependencies": {
5 | "@rollup/plugin-babel": "^5.2.0"
6 | },
7 | "peerDependencies": {
8 | "@babel/core": "^7.0.0",
9 | "freshie": "*"
10 | },
11 | "publishConfig": {
12 | "access": "public"
13 | }
14 | }
--------------------------------------------------------------------------------
/packages/@freshie/plugin.babel/readme.md:
--------------------------------------------------------------------------------
1 | # @freshie/plugin.babel
2 |
3 | ## Install
4 |
5 | ```sh
6 | $ npm install --save-dev @babel/core @freshie/plugin.babel
7 | ```
8 |
--------------------------------------------------------------------------------
/packages/@freshie/plugin.esbuild/config.js:
--------------------------------------------------------------------------------
1 | exports.esbuild = function (config, context) {
2 | config.define = config.define || {};
3 | config.include = /\.[jt]sx?$/; // default - TODO: options.ui.extensions ?
4 | config.target = context.ssr ? 'node12.18.0' : 'es2020';
5 | }
6 |
7 | exports.rollup = function (config, context, options) {
8 | Object.assign(options.esbuild.define, options.replace);
9 |
10 | config.plugins.push(
11 | require('rollup-plugin-esbuild')({
12 | ...options.esbuild,
13 | minify: context.minify,
14 | })
15 | );
16 | }
17 |
--------------------------------------------------------------------------------
/packages/@freshie/plugin.esbuild/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.0.5",
3 | "name": "@freshie/plugin.esbuild",
4 | "dependencies": {
5 | "rollup-plugin-esbuild": "^2.5.2"
6 | },
7 | "peerDependencies": {
8 | "esbuild": "^0.7.0",
9 | "freshie": "*"
10 | },
11 | "publishConfig": {
12 | "access": "public"
13 | }
14 | }
--------------------------------------------------------------------------------
/packages/@freshie/plugin.esbuild/readme.md:
--------------------------------------------------------------------------------
1 | # @freshie/plugin.esbuild
2 |
3 | ## Install
4 |
5 | ```sh
6 | $ npm install --save-dev esbuild @freshie/plugin.esbuild
7 | ```
8 |
--------------------------------------------------------------------------------
/packages/@freshie/plugin.postcss/assets.js:
--------------------------------------------------------------------------------
1 | const { copyFileSync, existsSync, readFileSync, mkdirSync } = require('fs');
2 | const { dirname, isAbsolute, parse, resolve } = require('path');
3 | const { createHash } = require('crypto');
4 | const postcss = require('postcss');
5 |
6 | const RGX = /(url\(\s*['"]?)([^"')]+)(["']?\s*\))/g;
7 |
8 | function toHash(filename, source) {
9 | const hmac = createHash('shake256', { outputLength: 16 });
10 | return hmac.update(source).digest('hex');
11 | }
12 |
13 | function noop() {
14 | //
15 | }
16 |
17 | // TODO: Rework for postcss 8.x
18 | module.exports = postcss.plugin('freshie/postcss.assets', (opts={}) => {
19 | const { hash, filters, write, target } = opts;
20 |
21 | let toWrite;
22 | let mkdir = false;
23 | const Cache = new Map;
24 |
25 | if (write) {
26 | toWrite = write;
27 | mkdir = true;
28 | } else {
29 | // TODO: throw error no target?
30 | const dir = resolve('.', target);
31 |
32 | toWrite = (input, output) => {
33 | if (!mkdir) {
34 | mkdirSync(dir, { recursive: true });
35 | mkdir = true;
36 | }
37 | copyFileSync(input, resolve(dir, output));
38 | }
39 | }
40 |
41 | const toHasher = hash || toHash;
42 | const toFilter = typeof filters === 'function' ? filters : noop;
43 |
44 | return function (styles) {
45 | const file = styles.source.input.file;
46 |
47 | styles.walkDecls(decl => {
48 | if (!RGX.test(decl.value)) return;
49 |
50 | decl.value = decl.value.replace(RGX, (full, open, inner, close) => {
51 | let tmp = Cache.get(inner);
52 | if (tmp) return tmp;
53 |
54 | if (inner.startsWith('data:')) {
55 | return full; // url(data:...)
56 | }
57 |
58 | tmp = toFilter(inner, file, decl.value);
59 |
60 | if (tmp && RGX.test(tmp)) {
61 | Cache.set(inner, tmp);
62 | return tmp; // url(...)
63 | }
64 |
65 | tmp = tmp || inner;
66 |
67 | if (tmp.charAt(0) === '/') {
68 | tmp = open + tmp + close;
69 | Cache.set(inner, tmp);
70 | return tmp; // url(/...)
71 | }
72 |
73 | if (/^(https?:)?\/\//.test(tmp)) {
74 | tmp = open + tmp + close;
75 | Cache.set(inner, tmp);
76 | return tmp; // url(http://)
77 | }
78 |
79 | if (!isAbsolute(tmp)) {
80 | tmp = resolve(dirname(file), tmp);
81 | }
82 |
83 | let xyz = Cache.get(tmp);
84 | if (xyz) return xyz;
85 |
86 | if (!existsSync(tmp)) {
87 | throw new Error(`Asset file does not exist: "${tmp}"`);
88 | }
89 |
90 | let info = parse(tmp);
91 | let outfile = toHasher(tmp, readFileSync(tmp)) + info.ext;
92 |
93 | toWrite(tmp, outfile);
94 |
95 | tmp = open + '/' + outfile + close;
96 | Cache.set('/' + outfile, tmp);
97 | Cache.set(inner, tmp);
98 | return tmp; // url(/{hash}.{ext})
99 | });
100 |
101 | return decl;
102 | });
103 | }
104 | });
105 |
--------------------------------------------------------------------------------
/packages/@freshie/plugin.postcss/config.js:
--------------------------------------------------------------------------------
1 | const { join } = require('path');
2 |
3 | function installed(name) {
4 | try { return require.resolve(name) }
5 | catch (err) { return false }
6 | }
7 |
8 | exports.stylus = {
9 | //
10 | }
11 |
12 | exports.sass = {
13 | //
14 | }
15 |
16 | exports.cssnano = {
17 | //
18 | }
19 |
20 | exports.postcss = function (config, context) {
21 | const { sourcemap, isProd } = context;
22 |
23 | config.plugins = [].concat(config.plugins || []);
24 | config.sourcemap = sourcemap ? { inline: true } : false;
25 |
26 | config.modules = config.modules || {};
27 | config.modules.scopeBehaviour = 'local';
28 | config.modules.generateScopedName = '[name]__[local]___[hash:base64:5]';
29 |
30 | if (isProd) {
31 | config.modules.generateScopedName = '[hash:base64:5]';
32 | if (installed('autoprefixer')) {
33 | config.plugins.push(require('autoprefixer')());
34 | }
35 | }
36 | }
37 |
38 | // TODO: no-op writes/output when `ssr` context
39 | // Will be using DOM output links anyway; ssr useless
40 | exports.rollup = function (config, context, options) {
41 | const { isProd, minify } = context;
42 | const entries = options.alias.entries;
43 |
44 | if (isProd && minify && installed('cssnano')) {
45 | options.postcss.plugins.push(
46 | require('cssnano')(options.cssnano)
47 | );
48 | }
49 |
50 | options.postcss.server = context.ssr;
51 |
52 | // route-based CSS chunking/code-splitting
53 | options.postcss.extract = function (filename) {
54 | const relative = filename.replace(entries['~routes'], '');
55 | const match = /[\\\/+]?([^\\\/+]*)/i.exec(relative);
56 | if (!match) return 'index.css'; // commons
57 |
58 | let name = match[1].toLowerCase();
59 | if (!name.endsWith('.css')) name += '.css';
60 | return (name.startsWith('_') ? '' : 'r.') + name;
61 | };
62 |
63 | // Apply `~assets` alias to url() contents
64 | //=> support "~assets", "~@assets", or "@assets"
65 | options.postcss.assets = function (value) {
66 | if (/(\~?@|\~)assets[\\\/]+?/.test(value)) {
67 | let tmp = value.replace(/(\~?@|\~)assets[\\\/]+?/, '');
68 | return join(entries['~assets'], tmp);
69 | }
70 | };
71 |
72 | options.postcss.sass = { ...options.sass, ...options.postcss.sass };
73 | options.postcss.stylus = { ...options.stylus, ...options.postcss.stylus };
74 | options.postcss.less = { ...options.less, ...options.postcss.less };
75 |
76 | config.plugins.push(
77 | require('.')(options.postcss),
78 | );
79 | }
80 |
--------------------------------------------------------------------------------
/packages/@freshie/plugin.postcss/index.js:
--------------------------------------------------------------------------------
1 | const { basename } = require('path');
2 | const { existsSync, readFile, readFileSync } = require('fs');
3 | const { promisify } = require('util');
4 | const postcss = require('postcss');
5 |
6 | const read = promisify(readFile);
7 | let render_stylus, render_less, render_sass;
8 |
9 | function load(name) {
10 | try { return require(name) }
11 | catch (e) { throw new Error(`\nPlease install the "${name}" package:\n $ npm install --save-dev ${name}`) }
12 | }
13 |
14 | function stylus(filename, filedata, sourcemap, options={}) {
15 | if (!render_stylus) render_stylus = load('stylus');
16 |
17 | options.filename = filename;
18 | if (sourcemap) options.sourcemap={ comment: false };
19 |
20 | let ctx = render_stylus(filedata, options);
21 |
22 | return new Promise((res, rej) => {
23 | ctx.render((err, css) => {
24 | let map = sourcemap && ctx.sourcemap;
25 | return err ? rej(err) : res({ css, map });
26 | });
27 | });
28 | }
29 |
30 | function sass(filename, filedata, sourcemap, options={}) {
31 | if (!render_sass) render_sass = load('node-sass').render;
32 |
33 | const indentedSyntax = /\.sass$/.test(filename);
34 | options.data = filedata;
35 | options.file = filename;
36 |
37 | if (sourcemap) {
38 | options.outFile = filename.replace(/\.s[ac]ss$/, '.css');
39 | options.sourceMap = true;
40 | }
41 |
42 | return new Promise((res, rej) => {
43 | render_sass({ ...options, indentedSyntax }, (err, result) => {
44 | return err ? rej(err) : res({
45 | css: result.css.toString(),
46 | map: result.map && result.map.toString()
47 | });
48 | });
49 | });
50 | }
51 |
52 | function less(filename, filedata, sourcemap, options={}) {
53 | if (!render_less) {
54 | render_less = load('less').render;
55 | }
56 |
57 | options.filename = filename;
58 | if (sourcemap) options.sourceMap={};
59 | return render_less(filedata, options);
60 | }
61 |
62 | module.exports = function (opts={}) {
63 | const { plugins=[], assets, extract, sourcemap, server, ...rest } = opts;
64 |
65 | let toExtract = false;
66 | const FILES = new Map, REFS = new Map;
67 | if (typeof extract === 'string') toExtract = () => extract;
68 | else if (typeof extract === 'function') toExtract = extract;
69 | else if (extract === true) toExtract = () => 'bundle.css';
70 |
71 | const toMap = sourcemap != null && !!sourcemap;
72 | const RUNTIME = require.resolve('./runtime.js');
73 | const IDENT = '!!~freshie.postcss.runtime~!!';
74 |
75 | return {
76 | name: 'freshie/postcss',
77 |
78 | resolveId(id) {
79 | return id === IDENT ? RUNTIME : null;
80 | },
81 |
82 | load(id) {
83 | if (!/\.(css|s[ac]ss|less|styl(us)?)$/.test(id)) return null;
84 | return existsSync(id) && read(id, 'utf8') || null;
85 | },
86 |
87 | async transform(source, id) {
88 | let css, file, tmp, map;
89 | if (/\.css$/.test(id)) {
90 | css = source;
91 | file = id;
92 | } else if (/\.styl(us)?$/.test(id)) {
93 | tmp = await stylus(id, source, toMap, rest.stylus);
94 | file = id.replace(/\.styl(us)?$/, '.css');
95 | css=tmp.css; map=tmp.map;
96 | } else if (/\.less$/.test(id)) {
97 | tmp = await less(id, source, toMap, rest.less);
98 | file = id.replace(/\.less$/, '.css');
99 | css=tmp.css; map=tmp.map;
100 | } else if (/\.s[ac]ss$/.test(id)) {
101 | tmp = await sass(id, source, toMap, rest.sass);
102 | file = id.replace(/\.s[ac]ss$/, '.css');
103 | css=tmp.css; map=tmp.map;
104 | } else {
105 | return null;
106 | }
107 |
108 | const toWrite = (file, data) => {
109 | this.emitFile({
110 | type: 'asset',
111 | fileName: file,
112 | source: data,
113 | });
114 | };
115 |
116 | const copy = [
117 | ...plugins,
118 | require('./assets')({
119 | filters: assets,
120 | write(input, output) {
121 | let content = readFileSync(input);
122 | return toWrite(output, content);
123 | }
124 | }),
125 | ].filter(Boolean);
126 |
127 | let Manifest = {};
128 | if (rest.modules) {
129 | copy.push(
130 | require('postcss-modules')({
131 | ...Object(rest.modules),
132 | getJSON(file, mapping) {
133 | // TODO: config.modules.getJSON proxy
134 | Manifest = mapping;
135 | }
136 | })
137 | );
138 | }
139 |
140 | if (sourcemap && map) {
141 | map = { ...sourcemap, prev: map };
142 | } else if (!sourcemap) {
143 | map = false;
144 | }
145 |
146 | const output = await postcss(copy).process(css, {
147 | ...rest, map, from: file,
148 | });
149 |
150 | if (toExtract) {
151 | file = toExtract(file);
152 | }
153 |
154 | // TODO: handle external sourcemap: `if (sourcemap && output.map)`
155 | // NOTE: `sourcemap.inline` already handled
156 | // console.log('FINAL OUTPUT', output.map);
157 |
158 | const content = (FILES.get(file) || '') + output.css;
159 | FILES.set(file, content); // full asset source
160 |
161 | let loader = '';
162 |
163 | if (!server) {
164 | const ref = REFS.get(file) || this.emitFile({
165 | type: 'asset',
166 | // sets `source` later
167 | name: basename(file),
168 | });
169 |
170 | REFS.set(file, ref);
171 |
172 | loader += `
173 | import { link } from "${IDENT}";
174 | link(import.meta.ROLLUP_FILE_URL_${ref});
175 | `;
176 | }
177 |
178 | for (let key in Manifest) {
179 | loader += `\nexport const ${key} = ${JSON.stringify(Manifest[key])};`;
180 | }
181 |
182 | return {
183 | code: loader.replace(/^\s+/gm, ''),
184 | // moduleSideEffects: true,
185 | };
186 | },
187 |
188 | renderStart() {
189 | if (server) return;
190 | for (let [file, ref] of REFS) {
191 | let content = FILES.get(file);
192 | this.setAssetSource(ref, content);
193 | FILES.delete(file); REFS.delete(file);
194 | }
195 | }
196 | };
197 | }
198 |
--------------------------------------------------------------------------------
/packages/@freshie/plugin.postcss/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.0.6",
3 | "name": "@freshie/plugin.postcss",
4 | "dependencies": {
5 | "postcss-modules": "^3.2.0"
6 | },
7 | "peerDependencies": {
8 | "autoprefixer": "^9.8.0",
9 | "cssnano": "^4.1.10",
10 | "freshie": "*",
11 | "less": "^3.12.0",
12 | "node-sass": "^4.14.0",
13 | "postcss": "^7.0.32",
14 | "stylus": "^0.54.0"
15 | },
16 | "peerDependenciesMeta": {
17 | "autoprefixer": {
18 | "optional": true
19 | },
20 | "cssnano": {
21 | "optional": true
22 | },
23 | "less": {
24 | "optional": true
25 | },
26 | "node-sass": {
27 | "optional": true
28 | },
29 | "stylus": {
30 | "optional": true
31 | }
32 | },
33 | "publishConfig": {
34 | "access": "public"
35 | }
36 | }
--------------------------------------------------------------------------------
/packages/@freshie/plugin.postcss/readme.md:
--------------------------------------------------------------------------------
1 | # @freshie/plugin.postcss
2 |
3 | ## Install
4 |
5 | ```sh
6 | $ npm install --save-dev postcss @freshie/plugin.postcss
7 | ```
8 |
9 | ***Optional***
10 |
11 | > Additional modules that, if installed, `@freshie/plugin.postcss` will attach.
12 |
13 | ```sh
14 | $ npm install --save-dev autoprefixer cssnano
15 | ```
16 |
17 | ## Preprocessors
18 |
19 | > Note: Only `stylus` is supported right now.
20 |
21 | Bring your desired preprocessor flavor, if any~!
22 |
23 | Simply install the preprocessor itself; for example:
24 |
25 | ```sh
26 | $ npm install --save-dev stylus
27 | ```
28 |
29 | In most cases, that's all you need to do – `@freshie/plugin.postcss` will invoke the preprocessor when its extension(s) is/are detected. However, should you need to send the preprocessor some configuration, modify its configuration key within your `freshie.config.js` file; for example:
30 |
31 | ```js
32 | // freshie.config.js
33 | exports.stylus = {
34 | // custom options
35 | }
36 | ```
37 |
38 | ***Direct Usage***
39 |
40 | If you are using this plugin directly (AKA, you **are not** using freshie), you may define preprocessor configuration via plugin options:
41 |
42 | ```js
43 | // rollup.config.js
44 | import Postcss from '@freshie/plugin.postcss';
45 |
46 | export default {
47 | // ...
48 | plugins: [
49 | Postcss({
50 | stylus: {
51 | // custom options
52 | }
53 | })
54 | ]
55 | }
56 | ```
57 |
--------------------------------------------------------------------------------
/packages/@freshie/plugin.postcss/runtime.js:
--------------------------------------------------------------------------------
1 | var cache = [];
2 |
3 | export function link(href, tmp) {
4 | if (!cache.length) {
5 | for (var i=0, arr=document.styleSheets; tmp = arr[i]; i++) {
6 | if (cache.push(tmp.href) && tmp.href === href) return;
7 | }
8 | }
9 | (tmp = document.createElement('link')).rel='stylesheet';
10 | document.head.appendChild(tmp);
11 | cache.push(tmp.href = href);
12 | }
13 |
--------------------------------------------------------------------------------
/packages/@freshie/plugin.typescript/config.js:
--------------------------------------------------------------------------------
1 | exports.typescript = function (config, context) {
2 | const override = config.tsconfigOverride || {};
3 | override.compilerOptions = override.compilerOptions || {};
4 | override.compilerOptions.watch = !context.isProd;
5 | config.tsconfigOverride = override;
6 | }
7 |
8 | exports.rollup = function (config, context, options) {
9 | config.plugins.push(
10 | require('rollup-plugin-typescript2')(options.typescript)
11 | );
12 | }
13 |
--------------------------------------------------------------------------------
/packages/@freshie/plugin.typescript/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.0.3",
3 | "name": "@freshie/plugin.typescript",
4 | "dependencies": {
5 | "rollup-plugin-typescript2": "^0.28.0"
6 | },
7 | "peerDependencies": {
8 | "typescript": ">=3.9.0",
9 | "freshie": "*"
10 | },
11 | "publishConfig": {
12 | "access": "public"
13 | }
14 | }
--------------------------------------------------------------------------------
/packages/@freshie/plugin.typescript/readme.md:
--------------------------------------------------------------------------------
1 | # @freshie/plugin.typescript
2 |
3 | ## Install
4 |
5 | ```sh
6 | $ npm install --save-dev typescript @freshie/plugin.typescript
7 | ```
8 |
--------------------------------------------------------------------------------
/packages/@freshie/ssr.node/config.js:
--------------------------------------------------------------------------------
1 | const { join } = require('path');
2 |
3 | exports.ssr = function (config) {
4 | config.type = 'node';
5 | // set fallback entry point
6 | config.entry = join(__dirname, 'entry.js');
7 | }
8 |
9 | exports.rollup = function (config, context) {
10 | if (!context.ssr) return;
11 | config.output.format = 'cjs';
12 | config.output.esModule = false;
13 | config.external = [
14 | ...(config.external || []),
15 | ...require('module').builtinModules,
16 | ];
17 | }
18 |
--------------------------------------------------------------------------------
/packages/@freshie/ssr.node/entry.js:
--------------------------------------------------------------------------------
1 | import { ssr } from '!!~ui~!!'; // alias
2 | import { start } from './index';
3 |
4 | const { PORT=3000 } = process.env;
5 |
6 | start({ render: ssr, port: PORT });
7 |
--------------------------------------------------------------------------------
/packages/@freshie/ssr.node/index.d.ts:
--------------------------------------------------------------------------------
1 | export function setup(options: TODO): TODO;
2 | export function middleware(options: TODO): TODO;
3 | export function start(options: TODO): TODO;
4 |
--------------------------------------------------------------------------------
/packages/@freshie/ssr.node/index.js:
--------------------------------------------------------------------------------
1 | import sirv from 'sirv';
2 | import { join } from 'path';
3 | import { resolve } from 'url';
4 | import parse from '@polka/url';
5 | import regexparam from 'regexparam';
6 | import { createServer } from 'http';
7 | import { HTML } from '!!~html~!!';
8 |
9 | const Tree = new Map;
10 | const ERRORS = { /* */ };
11 |
12 | function prepare(Tags, extra={}) {
13 | let i=0, tmp, loaders=[], views=[];
14 | for (; i < Tags.length; i++) {
15 | tmp = Tags[i];
16 | views.push(tmp.default);
17 | if (tmp.preload) loaders.push(tmp.preload);
18 | }
19 | return { ...extra, loaders, views };
20 | }
21 |
22 | // NOTE: ideally `layout` here
23 | function define(route, ...Tags) {
24 | let { keys, pattern } = regexparam(route);
25 | let entry = prepare(Tags, { keys });
26 | Tree.set(pattern, entry);
27 | }
28 |
29 | function toError(status, message='') {
30 | const error = new Error(message);
31 | error.status = status;
32 | throw error;
33 | }
34 |
35 | function find(pathname) {
36 | let rgx, data, match;
37 | let j=0, arr, params={};
38 | for ([rgx, data] of Tree) {
39 | arr = data.keys;
40 | if (arr.length > 0) {
41 | match = pathname.match(rgx);
42 | if (match === null) continue;
43 | for (j=0; j < arr.length;) params[arr[j]]=match[++j];
44 | return { params, loaders:data.loaders, views:data.views };
45 | } else if (rgx.test(pathname)) {
46 | return { params, loaders:data.loaders, views:data.views };
47 | }
48 | }
49 | }
50 |
51 | export function setup() {
52 | /* */
53 | return Tree;
54 | }
55 |
56 | // TODO: file server (sirv)
57 | // TODO: tie `sirv` existence to `options.ssr.*` thing
58 | export function start(options={}) {
59 | const { decode, port, render } = options;
60 | setup(); //=> attach app routes
61 |
62 | const assets = true && sirv(
63 | join(__dirname, '..', 'client'),
64 | { dev: __DEV__ } // all from options.ssr?
65 | );
66 |
67 | // TODO: req.url vs req.href disparity
68 | async function draw(req, route, context) {
69 | let props = { url: req.href };
70 |
71 | if (route.loaders.length > 0) {
72 | await Promise.all(
73 | route.loaders.map(p => p(req, context))
74 | ).then(list => {
75 | // TODO? deep merge props
76 | Object.assign(props, ...list);
77 | });
78 | }
79 |
80 | return render(route.views, props);
81 | }
82 |
83 | return createServer(async (req, res) => {
84 | let page={}, request=parse(req, decode);
85 | let route, isAsset, context={ status: 0 };
86 | context.headers = { 'Content-Type': 'text/html;charset=utf-8' };
87 |
88 | request.query = request.query || {};
89 | request.headers = req.headers;
90 | request.params = {};
91 |
92 | try {
93 | if (req.method !== 'GET' && req.method !== 'HEAD') {
94 | return toError(405);
95 | }
96 |
97 | route = find(request.pathname);
98 | if (!route && !assets) return toError(404);
99 | if (isAsset = !route) return assets(req, res, () => {
100 | return (isAsset=false,toError(404));
101 | });
102 |
103 | request.params = route.params;
104 | page = await draw(request, route, context);
105 | } catch (err) {
106 | context.error = err;
107 | context.status = context.status || err.statusCode || err.status || 500;
108 | // look up error by specificity
109 | const key = String(context.status);
110 | const route = ERRORS[key] || ERRORS[key[0] + 'xx'] || ERRORS['xxx']
111 | page = await draw(request, route, context);
112 | } finally {
113 | if (isAsset) return; // handled
114 | if (context.redirect) {
115 | context.headers.location = resolve(request.href, context.redirect);
116 | context.status = (context.status > 300 && context.status < 400) ? context.status : 302;
117 | }
118 | // props.head=head; props.body=body;
119 | // TODO: static HTML vs HTML component file
120 | res.writeHead(context.status || 200, context.headers);
121 | let output = HTML.replace(/<\/body>/, page.body + '