zuby 1.0.6 → 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,8 +1,10 @@
1
1
  # Zuby.js
2
+
2
3
  Zuby.js is a simple [Preact](https://preactjs.com/) framework for building JS apps that can combine the best of the SPA and MPA worlds.
3
- The framework itself is based on [Vite](https://vitejs.dev/).
4
+ The framework itself is based on [Vite](https://vitejs.dev/).
4
5
 
5
6
  ## Features:
7
+
6
8
  - File-based routing (with similar syntax to Next.js)
7
9
  - Build-time HTML pre-rendering
8
10
  - Automatic for static paths
@@ -13,28 +15,36 @@ The framework itself is based on [Vite](https://vitejs.dev/).
13
15
  - Built-in i18n localization support
14
16
 
15
17
  Missing features:
18
+
16
19
  - Server-side rendering
17
20
 
18
21
  ## For contributors
22
+
19
23
  ### Repository setup
24
+
20
25
  Please see the [NVM guide](https://github.com/nvm-sh/nvm#installing-and-updating) how to install specific Node.js version on your device.
21
26
  To set up this repository, run the following commands:
22
- ```
27
+
28
+ ```
23
29
  git clone git@gitlab.com:futrou/zuby.js.git
24
30
  cd zuby.js
25
31
  npm install
26
- ```
32
+ ```
27
33
 
28
34
  ### Recommended versions
35
+
29
36
  - Node.js: 18
30
37
  - npm: 9
31
38
 
32
39
  ### Releases
40
+
33
41
  Please follow these steps to release a new stable version of the package:
42
+
34
43
  1. Check the current version in `package.json`. This version will be released. The patch version is automatically increased by CI and pushed to the master branch after each release. If you want to release a minor or major version, please change the version manually. Otherwise, you can skip this step.
35
44
  2. Go to the page with [repository tags](https://gitlab.com/futrou/zuby.js/-/tags) and create a new tag from'master' with the version from `package.json` as the name (in the following format, `v1.0.0`).
36
45
  3. This will trigger the CI pipeline, which will release the package, generate a changelog, and create a new release in the Gitlab UI.
37
46
  4. That's it! 🎉 You can check the new release on [Gitlab releases page](https://gitlab.com/futrou/zuby.js/-/releases) and [NPM package page](https://www.npmjs.com/package/zuby).
38
47
 
39
48
  ## License
40
- MIT
49
+
50
+ MIT
package/branding.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export declare const getBrand: () => string;
2
+ export declare const getTitle: (title: string) => string;
package/branding.js ADDED
@@ -0,0 +1,9 @@
1
+ import chalk from 'chalk';
2
+ import { getZubyPackageJson } from './config.js';
3
+ const { name, version } = getZubyPackageJson();
4
+ export const getBrand = () => {
5
+ return name.charAt(0).toUpperCase() + name.slice(1) + '.js';
6
+ };
7
+ export const getTitle = (title) => {
8
+ return `${chalk.bgYellow.bold.whiteBright(` ${getBrand()} `)}${chalk.yellowBright.bgWhite(` v${version} `)} ${title}`;
9
+ };
@@ -0,0 +1,2 @@
1
+ import { BuildCommandOptions } from '../types.js';
2
+ export default function build(options: BuildCommandOptions): Promise<void>;
@@ -0,0 +1,15 @@
1
+ import { getViteConfig, getZubyConfig } from '../config.js';
2
+ import chalk from 'chalk';
3
+ import { getTitle } from '../branding.js';
4
+ import { build as viteBuild } from 'vite';
5
+ export default async function build(options) {
6
+ const zubyConfig = await getZubyConfig(options.configFile);
7
+ const viteConfig = getViteConfig(zubyConfig);
8
+ const logger = zubyConfig.customLogger;
9
+ logger?.info(getTitle(chalk.gray(`building for production...`)));
10
+ await viteBuild({
11
+ configFile: false,
12
+ ...viteConfig,
13
+ });
14
+ logger?.info(`Done! 🎉`);
15
+ }
@@ -0,0 +1,2 @@
1
+ import { ServerCommandOptions } from '../types.js';
2
+ export default function dev(options: ServerCommandOptions): Promise<void>;
@@ -0,0 +1,7 @@
1
+ import server from './server.js';
2
+ export default async function dev(options) {
3
+ return server({
4
+ ...options,
5
+ mode: 'development',
6
+ });
7
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import { getZubyPackageJson } from '../config.js';
4
+ import dev from './dev.js';
5
+ import build from './build.js';
6
+ import preview from './preview.js';
7
+ const { name, description, version } = getZubyPackageJson();
8
+ const program = new Command().name(name).description(description).version(version);
9
+ program
10
+ .command('dev')
11
+ .description('Starts the development server')
12
+ .option('--port', 'Port to use for the dev server', '3000')
13
+ .option('--host', 'Host to use for the dev server', 'localhost')
14
+ .option('--config-file', 'The relative path to file with Zuby config', 'zuby.config.mjs')
15
+ .action(async (options) => dev(options));
16
+ program
17
+ .command('preview')
18
+ .description('Starts the preview server for the production build')
19
+ .option('--port', 'Port to use for the dev server', '3000')
20
+ .option('--host', 'Host to use for the dev server', 'localhost')
21
+ .option('--config-file', 'The relative path to file with Zuby config', 'zuby.config.mjs')
22
+ .action(async (options) => preview(options));
23
+ program
24
+ .command('build')
25
+ .description('Builds the app for production')
26
+ .option('--config-file', 'The relative path to file with Zuby config', 'zuby.config.mjs')
27
+ .action(async (options) => build(options));
28
+ program.parse(process.argv);
@@ -0,0 +1,2 @@
1
+ import { ServerCommandOptions } from '../types.js';
2
+ export default function preview(options: ServerCommandOptions): Promise<void>;
@@ -0,0 +1,13 @@
1
+ import server from './server.js';
2
+ import { getZubyConfig } from "../config.js";
3
+ import { existsSync } from "fs";
4
+ export default async function preview(options) {
5
+ const zubyConfig = await getZubyConfig(options.configFile);
6
+ if (zubyConfig.outDir && !existsSync(zubyConfig.outDir)) {
7
+ throw new Error(`The outDir '${zubyConfig.outDir}' does not exist. Did you forget to run 'zuby build' first?`);
8
+ }
9
+ return server({
10
+ ...options,
11
+ mode: 'production',
12
+ });
13
+ }
@@ -0,0 +1,2 @@
1
+ import { ServerCommandOptions } from '../types.js';
2
+ export default function server(options: ServerCommandOptions): Promise<void>;
@@ -0,0 +1,32 @@
1
+ import { createServer } from 'vite';
2
+ import { getViteConfig, getZubyConfig } from '../config.js';
3
+ import { performance } from 'node:perf_hooks';
4
+ import chalk from 'chalk';
5
+ import { getTitle } from '../branding.js';
6
+ export default async function server(options) {
7
+ const port = Number(options.port ?? 3000);
8
+ const host = options.host ?? 'localhost';
9
+ const mode = options.mode ?? 'development';
10
+ const zubyConfig = await getZubyConfig(options.configFile);
11
+ const viteConfig = getViteConfig(zubyConfig);
12
+ const logger = zubyConfig.customLogger;
13
+ const server = await createServer({
14
+ ...viteConfig,
15
+ server: {
16
+ port,
17
+ host,
18
+ ...viteConfig.server,
19
+ },
20
+ mode,
21
+ });
22
+ if (!server.httpServer) {
23
+ throw new Error('HTTP server not available');
24
+ }
25
+ const startTime = performance.now();
26
+ await server.listen();
27
+ const readyTime = performance.now();
28
+ logger?.info(` ${getTitle(chalk.gray(`started in ${Math.round(readyTime - startTime)}ms`))}\r\n`);
29
+ logger?.info(` ┃ Mode ${mode}`);
30
+ logger?.info(` ┃ Local http://${server.config.server.host}:${server.config.server.port}/`);
31
+ logger?.info(` ┃ Network ${chalk.gray('use --host and --port to expose')}\r\n`);
32
+ }
package/config.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ import { ZubyConfig } from './types.js';
2
+ import { UserConfig as ViteUserConfig } from 'vite';
3
+ export declare const defineConfig: (config: ZubyConfig) => ZubyConfig;
4
+ export declare const getZubyConfig: (configFile?: string, cache?: boolean) => Promise<ZubyConfig>;
5
+ export declare const getViteConfig: (config: ZubyConfig) => ViteUserConfig;
6
+ export declare const getZubyPackageJson: (cache?: boolean) => any;
package/config.js ADDED
@@ -0,0 +1,69 @@
1
+ import { ZUBY_CONFIG_FILE } from './constants.js';
2
+ import { fileURLToPath } from 'url';
3
+ import { existsSync, readFileSync } from 'fs';
4
+ import { dirname, resolve } from 'path';
5
+ import { createLogger } from './logger.js';
6
+ let zubyConfig;
7
+ let packageJson;
8
+ export const defineConfig = (config) => {
9
+ // Default values
10
+ config.srcDir = config.srcDir ?? './';
11
+ config.publicDir = config.publicDir ?? 'public';
12
+ config.outDir = config.outDir ?? 'build';
13
+ config.output = config.output ?? 'static';
14
+ config.prerenderPaths = config.prerenderPaths ?? [];
15
+ config.customLogger =
16
+ config.customLogger ??
17
+ createLogger(config.logLevel, {
18
+ allowClearScreen: false,
19
+ prefix: '[Zuby]',
20
+ });
21
+ config.logLevel = config.logLevel ?? 'info';
22
+ config.vite = config.vite ?? {};
23
+ config.vite.build = config.vite?.build ?? {};
24
+ return config;
25
+ };
26
+ export const getZubyConfig = async (configFile = ZUBY_CONFIG_FILE, cache = true) => {
27
+ // Return the cached config if it exists
28
+ if (cache && zubyConfig)
29
+ return zubyConfig;
30
+ // Find the config file with various extensions
31
+ const foundConfigFile = [
32
+ configFile,
33
+ configFile.replace(/\.mjs$/, '.js'),
34
+ ].find((file) => existsSync(file));
35
+ if (!configFile) {
36
+ throw new Error(`Config file does not exist: ${configFile}`);
37
+ }
38
+ const exportedConfig = await import(`file:///${process.cwd().replace(/\\/g, '/')}/${foundConfigFile}`);
39
+ // Find the default export and cache the config
40
+ zubyConfig = exportedConfig?.default ?? exportedConfig;
41
+ if (!zubyConfig) {
42
+ throw new Error(`No valid default export found in config file: ${foundConfigFile}`);
43
+ }
44
+ return zubyConfig;
45
+ };
46
+ export const getViteConfig = (config) => {
47
+ // Generates valid vite build config from the zuby config
48
+ return {
49
+ mode: 'production',
50
+ root: config.srcDir,
51
+ base: '/',
52
+ ...config.vite,
53
+ build: {
54
+ outDir: config.outDir,
55
+ ...config.vite?.build,
56
+ },
57
+ logLevel: config.logLevel,
58
+ customLogger: config.customLogger,
59
+ };
60
+ };
61
+ export const getZubyPackageJson = (cache = true) => {
62
+ if (cache && packageJson)
63
+ return packageJson;
64
+ const __filename = fileURLToPath(import.meta.url);
65
+ const __dirname = dirname(__filename);
66
+ // Cache the package.json content
67
+ packageJson = JSON.parse(readFileSync(resolve(__dirname, '../package.json'), 'utf-8'));
68
+ return packageJson;
69
+ };
package/constants.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare const ZUBY_CONFIG_FILE = "zuby.config.mjs";
package/constants.js ADDED
@@ -0,0 +1 @@
1
+ export const ZUBY_CONFIG_FILE = 'zuby.config.mjs';
package/index.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from './types.js';
2
+ export { defineConfig } from './config.js';
package/index.js CHANGED
@@ -0,0 +1,2 @@
1
+ export * from './types.js';
2
+ export { defineConfig } from './config.js';
package/logger.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ import { ZubyLogger, LogLevel } from './types.js';
2
+ export interface LoggerOptions {
3
+ prefix?: string;
4
+ allowClearScreen?: boolean;
5
+ customLogger?: ZubyLogger;
6
+ }
7
+ export declare function transformMsg(msg: string): string;
8
+ export declare function createLogger(level?: LogLevel, options?: LoggerOptions): ZubyLogger;
package/logger.js ADDED
@@ -0,0 +1,107 @@
1
+ /* eslint no-console: 0 */
2
+ import readline from 'node:readline';
3
+ import chalk from 'chalk';
4
+ import { LogLevels } from './types.js';
5
+ let lastType;
6
+ let lastMsg;
7
+ let sameCount = 0;
8
+ function clearScreen() {
9
+ const repeatCount = process.stdout.rows - 2;
10
+ const blank = repeatCount > 0 ? '\n'.repeat(repeatCount) : '';
11
+ console.log(blank);
12
+ readline.cursorTo(process.stdout, 0, 0);
13
+ readline.clearScreenDown(process.stdout);
14
+ }
15
+ export function transformMsg(msg) {
16
+ if (msg.match(/vite v\d+.\d+.\d+/i))
17
+ return chalk.gray('Vite output:');
18
+ return msg;
19
+ }
20
+ export function createLogger(level = 'info', options = {}) {
21
+ if (options.customLogger) {
22
+ return options.customLogger;
23
+ }
24
+ const timeFormatter = new Intl.DateTimeFormat(undefined, {
25
+ hour: 'numeric',
26
+ minute: 'numeric',
27
+ second: 'numeric',
28
+ });
29
+ const loggedErrors = new WeakSet();
30
+ const { prefix = '[Zuby]', allowClearScreen = true } = options;
31
+ const thresh = LogLevels[level];
32
+ const canClearScreen = allowClearScreen && process.stdout.isTTY && !process.env.CI;
33
+ const clear = canClearScreen ? clearScreen : () => { };
34
+ function output(type, msg, options = {}) {
35
+ // Transforms all messages to have a consistent format.
36
+ msg = transformMsg(msg);
37
+ if (thresh >= LogLevels[type]) {
38
+ const method = type === 'info' ? 'log' : type;
39
+ const format = () => {
40
+ const tag = type === 'info'
41
+ ? chalk.cyan(chalk.bold(prefix))
42
+ : type === 'warn'
43
+ ? chalk.yellow(chalk.bold(prefix))
44
+ : chalk.red(chalk.bold(prefix));
45
+ if (options.timestamp) {
46
+ return `${chalk.dim(timeFormatter.format(new Date()))} ${tag} ${msg}`;
47
+ }
48
+ else {
49
+ return `${msg}`;
50
+ }
51
+ };
52
+ if (options.error) {
53
+ loggedErrors.add(options.error);
54
+ }
55
+ if (canClearScreen) {
56
+ if (type === lastType && msg === lastMsg) {
57
+ sameCount++;
58
+ clear();
59
+ console[method](format(), chalk.yellow(`(x${sameCount + 1})`));
60
+ }
61
+ else {
62
+ sameCount = 0;
63
+ lastMsg = msg;
64
+ lastType = type;
65
+ if (options.clear) {
66
+ clear();
67
+ }
68
+ console[method](format());
69
+ }
70
+ }
71
+ else {
72
+ console[method](format());
73
+ }
74
+ }
75
+ }
76
+ const warnedMessages = new Set();
77
+ const logger = {
78
+ hasWarned: false,
79
+ info(msg, opts) {
80
+ output('info', msg, opts);
81
+ },
82
+ warn(msg, opts) {
83
+ logger.hasWarned = true;
84
+ output('warn', msg, opts);
85
+ },
86
+ warnOnce(msg, opts) {
87
+ if (warnedMessages.has(msg))
88
+ return;
89
+ logger.hasWarned = true;
90
+ output('warn', msg, opts);
91
+ warnedMessages.add(msg);
92
+ },
93
+ error(msg, opts) {
94
+ logger.hasWarned = true;
95
+ output('error', msg, opts);
96
+ },
97
+ clearScreen(type) {
98
+ if (thresh >= LogLevels[type]) {
99
+ clear();
100
+ }
101
+ },
102
+ hasErrorLogged(error) {
103
+ return loggedErrors.has(error);
104
+ },
105
+ };
106
+ return logger;
107
+ }
package/package.json CHANGED
@@ -1,15 +1,33 @@
1
1
  {
2
2
  "name": "zuby",
3
- "version": "1.0.6",
4
- "description": "The Preact Framework for building SPA apps using Vite",
3
+ "version": "1.0.8",
4
+ "description": "Zuby.js is Preact Framework for building SPA apps using Vite",
5
5
  "type": "module",
6
- "main": "index.mjs",
6
+ "main": "./commands/index.js",
7
7
  "scripts": {
8
8
  "release": "npm publish --access public ./dist/ ",
9
9
  "bump-version": "npm version patch",
10
- "build": "rm -rf dist/ && mkdir dist && cp -rf src/* package.json README.md dist/",
10
+ "build": "rm -rf dist/ && tsc && cp -rf package.json README.md dist/",
11
+ "push-build": "npm run build && cd dist && npm link && cd ..",
12
+ "prettier": "prettier --write ./**/*",
11
13
  "test": "echo \"Error: no test specified\" && exit 1"
12
14
  },
15
+ "dependencies": {
16
+ "@preact/preset-vite": "^2.5.0",
17
+ "chalk": "^5.3.0",
18
+ "commander": "^11.0.0",
19
+ "html-minifier": "^4.0.0",
20
+ "vite": "^4.4.11",
21
+ "wouter-preact": "^2.12.0"
22
+ },
23
+ "devDependencies": {
24
+ "prettier": "^3.0.3",
25
+ "typescript": "^5.2.2",
26
+ "@types/node": "^20.8.3"
27
+ },
28
+ "bin": {
29
+ "zuby": "./commands/index.js"
30
+ },
13
31
  "bugs": {
14
32
  "url": "https://gitlab.com/futrou/zuby.js/-/issues",
15
33
  "email": "zuby@futrou.com"
@@ -25,5 +43,12 @@
25
43
  "spa",
26
44
  "framework",
27
45
  "web"
28
- ]
46
+ ],
47
+ "prettier": {
48
+ "singleQuote": true,
49
+ "semi": true,
50
+ "trailingComma": "es5",
51
+ "printWidth": 100,
52
+ "arrowParens": "avoid"
53
+ }
29
54
  }
package/types.d.ts ADDED
@@ -0,0 +1,130 @@
1
+ import { UserConfig as ViteUserConfig } from 'vite';
2
+ export interface ZubyConfig {
3
+ /**
4
+ * The relative path to directory were your zuby project is located in.
5
+ * @default ./
6
+ */
7
+ srcDir?: string;
8
+ /**
9
+ * The relative path to directory were the folder with the static files is located in.
10
+ * These files are copied to the output directory.
11
+ * @default public
12
+ */
13
+ publicDir?: string;
14
+ /**
15
+ * The relative path to directory were the build will be placed in.
16
+ * @default build
17
+ */
18
+ outDir?: string;
19
+ /**
20
+ * The build mode.
21
+ * @default static
22
+ */
23
+ output?: 'static';
24
+ /**
25
+ * The internalization config. If this is set, the site will be generated in multiple locales.
26
+ * @default undefined
27
+ * @example {
28
+ * locales: ['en', 'de', 'cs', 'pl'],
29
+ * defaultLocale: 'en'
30
+ * }
31
+ */
32
+ i18n?: {
33
+ locales: string[];
34
+ defaultLocale: string;
35
+ };
36
+ /**
37
+ * List of additional dynamic routes to pre-render during the build.
38
+ * The static paths are pre-rendered automatically and don't need to be specified here.
39
+ * @default []
40
+ * @example ['/products/1', '/products/2']
41
+ */
42
+ prerenderPaths?: string[];
43
+ /**
44
+ * The additional vite config, which will be merged with the default config.
45
+ */
46
+ vite?: ViteUserConfig;
47
+ /**
48
+ * The log level for both Zuby and all plugins.
49
+ * @default info
50
+ */
51
+ logLevel?: 'error' | 'warn' | 'info' | 'silent';
52
+ /**
53
+ * The custom logger for both Zuby, Vite and all plugins.
54
+ * @default ZubyLogger
55
+ */
56
+ customLogger?: ZubyLogger;
57
+ }
58
+ export interface BaseCommandOptions {
59
+ /**
60
+ * The path to the zuby config file.
61
+ */
62
+ configFile?: string;
63
+ }
64
+ export interface ServerCommandOptions extends BaseCommandOptions {
65
+ /**
66
+ * The port for the dev server.
67
+ * @default 3000
68
+ */
69
+ port?: number;
70
+ /**
71
+ * The host for the dev server.
72
+ * @default localhost
73
+ */
74
+ host?: string;
75
+ /**
76
+ * The mode for the server.
77
+ * @default development
78
+ */
79
+ mode?: 'development' | 'production';
80
+ }
81
+ export interface BuildCommandOptions extends BaseCommandOptions {
82
+ }
83
+ export type LogType = 'error' | 'warn' | 'info';
84
+ export type LogLevel = LogType | 'silent';
85
+ export interface ZubyLogger {
86
+ info(msg: string, options?: LogOptions): void;
87
+ warn(msg: string, options?: LogOptions): void;
88
+ warnOnce(msg: string, options?: LogOptions): void;
89
+ error(msg: string, options?: LogErrorOptions): void;
90
+ clearScreen(type: LogType): void;
91
+ hasErrorLogged(error: Error | RollupError): boolean;
92
+ hasWarned: boolean;
93
+ }
94
+ export interface LogOptions {
95
+ clear?: boolean;
96
+ timestamp?: boolean;
97
+ }
98
+ export interface LogErrorOptions extends LogOptions {
99
+ error?: Error | RollupError | null;
100
+ }
101
+ export declare const LogLevels: Record<LogLevel, number>;
102
+ export interface RollupError extends RollupLog {
103
+ name?: string;
104
+ stack?: string;
105
+ watchFiles?: string[];
106
+ }
107
+ export interface RollupLog {
108
+ binding?: string;
109
+ cause?: unknown;
110
+ code?: string;
111
+ exporter?: string;
112
+ frame?: string;
113
+ hook?: string;
114
+ id?: string;
115
+ ids?: string[];
116
+ loc?: {
117
+ column: number;
118
+ file?: string;
119
+ line: number;
120
+ };
121
+ message: string;
122
+ meta?: any;
123
+ names?: string[];
124
+ plugin?: string;
125
+ pluginCode?: unknown;
126
+ pos?: number;
127
+ reexporter?: string;
128
+ stack?: string;
129
+ url?: string;
130
+ }
package/types.js ADDED
@@ -0,0 +1,6 @@
1
+ export const LogLevels = {
2
+ silent: 0,
3
+ error: 1,
4
+ warn: 2,
5
+ info: 3,
6
+ };