zuby 1.0.45 → 1.0.47
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/commands/index.js +15 -0
- package/commands/info.d.ts +2 -0
- package/commands/info.js +14 -0
- package/commands/init.d.ts +1 -1
- package/commands/init.js +69 -34
- package/commands/upgrade.d.ts +3 -0
- package/commands/upgrade.js +80 -0
- package/config.js +2 -0
- package/constants.d.ts +4 -0
- package/constants.js +5 -0
- package/context/index.d.ts +1 -0
- package/context/types.d.ts +1 -0
- package/package.json +1 -1
- package/pageContext/index.d.ts +6 -0
- package/pageContext/index.js +7 -0
- package/plugins/contextPlugin/index.d.ts +2 -0
- package/plugins/contextPlugin/index.js +25 -3
- package/plugins/preloadPlugin/index.d.ts +7 -0
- package/plugins/preloadPlugin/index.js +27 -0
- package/plugins/prerenderPlugin/index.js +2 -2
- package/preload/index.d.ts +16 -0
- package/preload/index.js +87 -0
- package/server/index.js +13 -2
- package/server/zubyServer.js +6 -1
- package/templates/index.d.ts +4 -0
- package/templates/index.js +8 -0
- package/templates/types.d.ts +5 -0
- package/templates/types.js +4 -0
- package/types.d.ts +28 -3
package/commands/index.js
CHANGED
|
@@ -5,6 +5,8 @@ import dev from './dev.js';
|
|
|
5
5
|
import build from './build.js';
|
|
6
6
|
import preview from './preview.js';
|
|
7
7
|
import init from './init.js';
|
|
8
|
+
import info from './info.js';
|
|
9
|
+
import upgrade from './upgrade.js';
|
|
8
10
|
const { name, description, version } = getZubyPackageConfig();
|
|
9
11
|
const program = new Command().name(name).description(description).version(version);
|
|
10
12
|
program
|
|
@@ -30,5 +32,18 @@ program
|
|
|
30
32
|
program
|
|
31
33
|
.command('init')
|
|
32
34
|
.description('Initializes a new Zuby project')
|
|
35
|
+
.option('-p, --project-path <path>', 'The relative path to the project directory')
|
|
36
|
+
.option('-j, --jsx-provider-name <name>', 'The name of the JSX provider to use (react, preact)')
|
|
37
|
+
.option('-e, --example-name <name>', 'The name of the example project to use (basic)')
|
|
38
|
+
.option('-t, --use-typescript', 'Add this flag to use TypeScript instead of JavaScript')
|
|
33
39
|
.action(async (options) => init(options));
|
|
40
|
+
program
|
|
41
|
+
.command('info')
|
|
42
|
+
.description('Prints useful information about your setup')
|
|
43
|
+
.action(async (options) => info(options));
|
|
44
|
+
program
|
|
45
|
+
.command('upgrade')
|
|
46
|
+
.description('Upgrades Zuby to the latest compatible version')
|
|
47
|
+
.option('-t, --tag <tag>', 'The tag to upgrade to (e.g. v1, v2, latest)')
|
|
48
|
+
.action(async (options) => upgrade(options));
|
|
34
49
|
program.parse(process.argv);
|
package/commands/info.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { getZubyInternalConfig } from '../config.js';
|
|
2
|
+
import { getZubyPackageConfig } from '../packageConfig.js';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
export default async function info({ configFile }) {
|
|
5
|
+
const { outDir, output, plugins, customLogger: logger } = await getZubyInternalConfig(configFile);
|
|
6
|
+
const { version } = getZubyPackageConfig();
|
|
7
|
+
logger.info(`Zuby: ${version}`);
|
|
8
|
+
logger.info(`Node: ${process.version}`);
|
|
9
|
+
logger.info(`OS: ${os.type()} ${os.release()}`);
|
|
10
|
+
logger.info(`Output: ${output}`);
|
|
11
|
+
logger.info(`Output dir: ${outDir}`);
|
|
12
|
+
const pluginNames = plugins.map(plugin => plugin.name).join(', ');
|
|
13
|
+
logger.info(`Plugins: ${plugins.length ? pluginNames : 'none'}`);
|
|
14
|
+
}
|
package/commands/init.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { InitCommandOptions } from '../types.js';
|
|
2
|
-
export default function init(
|
|
2
|
+
export default function init({ projectPath, jsxProviderName, exampleName, useTypescript, }: InitCommandOptions): Promise<void>;
|
|
3
3
|
export declare function initExample(example: Example, options: InitOptions): Promise<void>;
|
|
4
4
|
export declare function getExamples(): Example[];
|
|
5
5
|
export interface Example {
|
package/commands/init.js
CHANGED
|
@@ -11,7 +11,7 @@ import { getZubyPackageConfig } from '../packageConfig.js';
|
|
|
11
11
|
import { globSync } from 'glob';
|
|
12
12
|
const __filename = fileURLToPath(import.meta.url);
|
|
13
13
|
const __dirname = dirname(__filename);
|
|
14
|
-
export default async function init(
|
|
14
|
+
export default async function init({ projectPath, jsxProviderName, exampleName, useTypescript, }) {
|
|
15
15
|
const logger = createLogger();
|
|
16
16
|
logger.clearScreen('info');
|
|
17
17
|
logger?.info(`${getTitle(`Let's get started with Zuby`)}\r\n`);
|
|
@@ -19,44 +19,79 @@ export default async function init(options) {
|
|
|
19
19
|
const examples = getExamples();
|
|
20
20
|
const defaultProjectName = 'my-zuby-app';
|
|
21
21
|
const defaultExample = examples.find(example => example.name === 'basic');
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
22
|
+
const jsxProviderNames = ['preact', 'react'];
|
|
23
|
+
const exampleNames = examples.map(example => example.name);
|
|
24
|
+
const validateProjectPath = (projectPath) => {
|
|
25
|
+
if (existsSync(projectPath)) {
|
|
26
|
+
return 'Project with this name already exists. Please choose a different one.';
|
|
27
|
+
}
|
|
28
|
+
return true;
|
|
29
|
+
};
|
|
30
|
+
const validateJsxProviderName = (name) => {
|
|
31
|
+
if (!jsxProviderNames.includes(name)) {
|
|
32
|
+
return `Invalid JSX provider name. Please choose one of the following: ${jsxProviderNames.join(',')}.`;
|
|
33
|
+
}
|
|
34
|
+
return true;
|
|
35
|
+
};
|
|
36
|
+
const validateExampleName = (name) => {
|
|
37
|
+
if (!exampleNames.includes(name)) {
|
|
38
|
+
return `Invalid example project name. Please choose one of the following: ${exampleNames.join(',')}.`;
|
|
39
|
+
}
|
|
40
|
+
return true;
|
|
41
|
+
};
|
|
42
|
+
if (!projectPath || validateProjectPath(projectPath) !== true) {
|
|
43
|
+
({ projectPath } = await inquirer.prompt([
|
|
44
|
+
{
|
|
45
|
+
type: 'input',
|
|
46
|
+
name: 'projectPath',
|
|
47
|
+
message: 'Where would you like to create your new project?',
|
|
48
|
+
default: `./${defaultProjectName}`,
|
|
49
|
+
prefix: `${chalk.yellowBright.bgWhite(' name ')} ❯ `,
|
|
50
|
+
validate: validateProjectPath,
|
|
51
|
+
},
|
|
52
|
+
]));
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
logger.info(`Project path: ${projectPath}`);
|
|
56
|
+
}
|
|
57
|
+
if (!jsxProviderName || validateJsxProviderName(jsxProviderName) !== true) {
|
|
58
|
+
({ jsxProviderName } = await inquirer.prompt([
|
|
59
|
+
{
|
|
60
|
+
type: 'list',
|
|
61
|
+
name: 'jsxProviderName',
|
|
62
|
+
message: 'In which JSX framework would you like to write your components?',
|
|
63
|
+
choices: jsxProviderNames,
|
|
64
|
+
default: jsxProviderNames?.[0],
|
|
65
|
+
prefix: `${chalk.yellowBright.bgWhite(' jsx ')} ❯ `,
|
|
66
|
+
validate: validateJsxProviderName,
|
|
67
|
+
},
|
|
68
|
+
]));
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
logger.info(`Jsx Provider: ${jsxProviderName}`);
|
|
72
|
+
}
|
|
73
|
+
if (!exampleName || validateExampleName(exampleName) !== true) {
|
|
74
|
+
({ exampleName } = await inquirer.prompt([
|
|
75
|
+
{
|
|
76
|
+
type: 'list',
|
|
77
|
+
name: 'exampleName',
|
|
78
|
+
message: 'Which Zuby example project would you like to use?',
|
|
79
|
+
choices: exampleNames,
|
|
80
|
+
default: defaultExample?.name,
|
|
81
|
+
prefix: `${chalk.yellowBright.bgWhite(' example ')} ❯ `,
|
|
82
|
+
validate: validateExampleName,
|
|
34
83
|
},
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
choices: ['preact', 'react'],
|
|
41
|
-
default: 'preact',
|
|
42
|
-
prefix: `${chalk.yellowBright.bgWhite(' jsx ')} ❯ `,
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
type: 'list',
|
|
46
|
-
name: 'exampleName',
|
|
47
|
-
message: 'Which Zuby example project would you like to use?',
|
|
48
|
-
choices: examples.map(example => example.name),
|
|
49
|
-
default: defaultExample?.name,
|
|
50
|
-
prefix: `${chalk.yellowBright.bgWhite(' example ')} ❯ `,
|
|
51
|
-
},
|
|
52
|
-
]);
|
|
53
|
-
let useTypescript = false;
|
|
84
|
+
]));
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
logger.info(`Example project: ${exampleName}`);
|
|
88
|
+
}
|
|
54
89
|
const selectedExample = examples.find(example => example.name === exampleName);
|
|
55
90
|
if (!selectedExample) {
|
|
56
91
|
logger.error(`Example ${exampleName} was not found`);
|
|
57
92
|
process.exit(1);
|
|
58
93
|
}
|
|
59
|
-
if (selectedExample?.isTs) {
|
|
94
|
+
if (useTypescript === undefined && selectedExample?.isTs) {
|
|
60
95
|
({ useTypescript } = await inquirer.prompt([
|
|
61
96
|
{
|
|
62
97
|
type: 'confirm',
|
|
@@ -67,7 +102,7 @@ export default async function init(options) {
|
|
|
67
102
|
},
|
|
68
103
|
]));
|
|
69
104
|
}
|
|
70
|
-
const projectName = projectPath
|
|
105
|
+
const projectName = projectPath?.split('/').pop() || defaultProjectName;
|
|
71
106
|
const zubyVersion = getZubyPackageConfig().version;
|
|
72
107
|
await initExample(selectedExample, {
|
|
73
108
|
projectName,
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { getZubyInternalConfig } from '../config.js';
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
|
3
|
+
import { resolve } from 'path';
|
|
4
|
+
import { execSync } from 'child_process';
|
|
5
|
+
import { getZubyPackageConfig } from '../packageConfig.js';
|
|
6
|
+
import { getTitle } from '../branding.js';
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
export default async function upgrade({ configFile, tag }) {
|
|
9
|
+
const { customLogger: logger } = await getZubyInternalConfig(configFile);
|
|
10
|
+
const packageJsonPath = resolve('package.json');
|
|
11
|
+
const { version: currentVersion } = getZubyPackageConfig();
|
|
12
|
+
const [currentMajor, _currentMinor, _currentPatch] = currentVersion.split('.');
|
|
13
|
+
if (!existsSync(packageJsonPath)) {
|
|
14
|
+
logger.error(`ERROR: Could not find package.json`);
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
18
|
+
const res = await fetch('https://registry.npmjs.org/-/package/zuby/dist-tags');
|
|
19
|
+
if (!res.ok) {
|
|
20
|
+
logger.error(`ERROR: Failed to fetch latest version from NPM.`);
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
const tags = (await res.json());
|
|
24
|
+
const latestVersion = tags.latest;
|
|
25
|
+
const selectedVersion = tags[tag || `v${currentMajor}`] || latestVersion;
|
|
26
|
+
logger.info(getTitle(`Checking...`));
|
|
27
|
+
if (currentVersion === latestVersion) {
|
|
28
|
+
logger.info(`You are on the latest version of Zuby.js ${currentVersion}`);
|
|
29
|
+
logger.info(`Well done! 🎉`);
|
|
30
|
+
process.exit(0);
|
|
31
|
+
}
|
|
32
|
+
if (selectedVersion !== latestVersion) {
|
|
33
|
+
logger.info(chalk.bold(`New major version of Zuby.js is available: ${latestVersion}`));
|
|
34
|
+
logger.info(`Run 'npx zuby upgrade --tag latest' if you're prepared to upgrade to it.`);
|
|
35
|
+
logger.info(`\r\n`);
|
|
36
|
+
}
|
|
37
|
+
if (currentVersion === selectedVersion) {
|
|
38
|
+
logger.info(`You are on the latest compatible version of Zuby.js ${currentVersion}`);
|
|
39
|
+
process.exit(0);
|
|
40
|
+
}
|
|
41
|
+
logger.info(`Upgrading Zuby.js to ${selectedVersion}`);
|
|
42
|
+
Object.keys({
|
|
43
|
+
...(packageJson.dependencies || {}),
|
|
44
|
+
...(packageJson.devDependencies || {}),
|
|
45
|
+
}).forEach(name => {
|
|
46
|
+
if (!name.match(/^zuby|@zubyjs\/(.+)$/i))
|
|
47
|
+
return;
|
|
48
|
+
if (packageJson.dependencies?.[name]) {
|
|
49
|
+
packageJson.dependencies[name] = selectedVersion;
|
|
50
|
+
}
|
|
51
|
+
if (packageJson.devDependencies?.[name]) {
|
|
52
|
+
packageJson.devDependencies[name] = selectedVersion;
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
logger.info(`Writing package.json`);
|
|
56
|
+
writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
|
|
57
|
+
if (installDependencies()) {
|
|
58
|
+
logger.info(`Zuby.js upgraded to ${selectedVersion}!`);
|
|
59
|
+
logger.info(`Well done! 🎉`);
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
logger.info(`We're done! 🎉 Now it's your turn.`);
|
|
63
|
+
logger.info(`Please run 'npm install', 'yarn install' etc.. with your favorite package manager to finish the upgrade.`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
export function installDependencies() {
|
|
67
|
+
if (existsSync('package-lock.json')) {
|
|
68
|
+
execSync('npm install', { stdio: 'inherit' });
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
if (existsSync('yarn.lock')) {
|
|
72
|
+
execSync('yarn install', { stdio: 'inherit' });
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
if (existsSync('pnpm-lock.yaml')) {
|
|
76
|
+
execSync('pnpm install', { stdio: 'inherit' });
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
return false;
|
|
80
|
+
}
|
package/config.js
CHANGED
|
@@ -10,6 +10,7 @@ import chunkNamingPlugin from './plugins/chunkNamingPlugin/index.js';
|
|
|
10
10
|
import manifestPlugin from './plugins/manifestPlugin/index.js';
|
|
11
11
|
import prerenderPlugin from './plugins/prerenderPlugin/index.js';
|
|
12
12
|
import standaloneBuildPlugin from './plugins/dependenciesPlugin/index.js';
|
|
13
|
+
import preloadPlugin from './plugins/preloadPlugin/index.js';
|
|
13
14
|
let zubyInternalConfig;
|
|
14
15
|
/**
|
|
15
16
|
* Returns the path to the ZubyConfig file.
|
|
@@ -148,6 +149,7 @@ export const getBuiltInPlugins = () => {
|
|
|
148
149
|
manifestPlugin(),
|
|
149
150
|
prerenderPlugin(),
|
|
150
151
|
standaloneBuildPlugin(),
|
|
152
|
+
preloadPlugin(),
|
|
151
153
|
];
|
|
152
154
|
};
|
|
153
155
|
/**
|
package/constants.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export declare const ZUBY_CONFIG_FILE = "zuby.config.mjs";
|
|
|
2
2
|
export declare const BUILD_CHUNKS_MANIFEST = "chunks-manifest.json";
|
|
3
3
|
export declare const CLIENT_CHUNKS_MANIFEST = "client-chunks-manifest.json";
|
|
4
4
|
export declare const SERVER_CHUNKS_MANIFEST = "server-chunks-manifest.json";
|
|
5
|
+
export declare const PREALOD_MANIFEST = "preload-manifest.json";
|
|
5
6
|
export declare const PAGES_MANIFEST = "pages-manifest.json";
|
|
6
7
|
export declare const HTTP_HEADERS: {
|
|
7
8
|
Age: string;
|
|
@@ -24,3 +25,6 @@ export declare const HTTP_HEADERS: {
|
|
|
24
25
|
XZubyCacheTTL: string;
|
|
25
26
|
XZubyCache: string;
|
|
26
27
|
};
|
|
28
|
+
export declare const ZUBY_USER_AGENTS: {
|
|
29
|
+
Prerenderer: string;
|
|
30
|
+
};
|
package/constants.js
CHANGED
|
@@ -2,6 +2,7 @@ export const ZUBY_CONFIG_FILE = 'zuby.config.mjs';
|
|
|
2
2
|
export const BUILD_CHUNKS_MANIFEST = 'chunks-manifest.json';
|
|
3
3
|
export const CLIENT_CHUNKS_MANIFEST = 'client-chunks-manifest.json';
|
|
4
4
|
export const SERVER_CHUNKS_MANIFEST = 'server-chunks-manifest.json';
|
|
5
|
+
export const PREALOD_MANIFEST = 'preload-manifest.json';
|
|
5
6
|
export const PAGES_MANIFEST = 'pages-manifest.json';
|
|
6
7
|
export const HTTP_HEADERS = {
|
|
7
8
|
// Standard HTTP headers
|
|
@@ -27,3 +28,7 @@ export const HTTP_HEADERS = {
|
|
|
27
28
|
XZubyCacheTTL: 'X-Zuby-Cache-TTL',
|
|
28
29
|
XZubyCache: 'X-Zuby-Cache',
|
|
29
30
|
};
|
|
31
|
+
export const ZUBY_USER_AGENTS = {
|
|
32
|
+
// Zuby user agents
|
|
33
|
+
Prerenderer: 'zuby-prerender',
|
|
34
|
+
};
|
package/context/index.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export declare class ZubyContext {
|
|
|
9
9
|
layouts?: import("../templates/types.js").LazyTemplate[] | undefined;
|
|
10
10
|
innerLayouts?: import("../templates/types.js").LazyTemplate[] | undefined;
|
|
11
11
|
handlers?: import("../templates/types.js").LazyTemplate[] | undefined;
|
|
12
|
+
loaders?: import("../templates/types.js").LazyTemplate[] | undefined;
|
|
12
13
|
} | undefined;
|
|
13
14
|
get site(): string | undefined;
|
|
14
15
|
get generator(): string | undefined;
|
package/context/types.d.ts
CHANGED
package/package.json
CHANGED
package/pageContext/index.d.ts
CHANGED
|
@@ -82,6 +82,7 @@ export declare class ZubyPageContext {
|
|
|
82
82
|
layouts?: import("../templates/types.js").LazyTemplate[] | undefined;
|
|
83
83
|
innerLayouts?: import("../templates/types.js").LazyTemplate[] | undefined;
|
|
84
84
|
handlers?: import("../templates/types.js").LazyTemplate[] | undefined;
|
|
85
|
+
loaders?: import("../templates/types.js").LazyTemplate[] | undefined;
|
|
85
86
|
} | undefined;
|
|
86
87
|
/**
|
|
87
88
|
* The object with props that should be passed to the page component.
|
|
@@ -166,4 +167,9 @@ export declare class ZubyPageContext {
|
|
|
166
167
|
* @example localizePath('/products/1', 'en') => /products/1
|
|
167
168
|
*/
|
|
168
169
|
localizePath(path: string, locale?: string | undefined): string;
|
|
170
|
+
/**
|
|
171
|
+
* Returns true if the current request
|
|
172
|
+
* was made by the Zuby.js pre-render build step.
|
|
173
|
+
*/
|
|
174
|
+
get isPrerendering(): boolean;
|
|
169
175
|
}
|
package/pageContext/index.js
CHANGED
|
@@ -218,4 +218,11 @@ export class ZubyPageContext {
|
|
|
218
218
|
return path;
|
|
219
219
|
return `/${locale}/${path}`.replace(/\/+/g, '/');
|
|
220
220
|
}
|
|
221
|
+
/**
|
|
222
|
+
* Returns true if the current request
|
|
223
|
+
* was made by the Zuby.js pre-render build step.
|
|
224
|
+
*/
|
|
225
|
+
get isPrerendering() {
|
|
226
|
+
return this._request?.headers?.get('user-agent') === 'zuby-prerender';
|
|
227
|
+
}
|
|
221
228
|
}
|
|
@@ -4,4 +4,6 @@ export default function index(): VitePlugin;
|
|
|
4
4
|
export declare function generateCompileTimeContextCode(ssr: boolean): Promise<string>;
|
|
5
5
|
export declare function generateTemplatesCode(ssr: boolean): Promise<string>;
|
|
6
6
|
export declare function generateTemplateCode(template: Template): Promise<string>;
|
|
7
|
+
export declare function generateImportCode(template: Template): string;
|
|
7
8
|
export declare function generateRenderCode(ssr: boolean): Promise<string>;
|
|
9
|
+
export declare function getStaticImportsCode(): string;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { getZubyInternalConfig } from '../../config.js';
|
|
2
|
+
import { SYNC_TEMPLATES } from '../../templates/types.js';
|
|
2
3
|
import { relative } from 'path';
|
|
3
4
|
import { normalizePath } from '../../utils/pathUtils.js';
|
|
4
5
|
import { getZubyPackageConfig } from '../../packageConfig.js';
|
|
5
|
-
import { getApps, getErrors, getHandlers, getInnerLayouts, getLayouts, getPages, getTemplates, } from '../../templates/index.js';
|
|
6
|
+
import { getApps, getErrors, getHandlers, getInnerLayouts, getLayouts, getLoaders, getPages, getTemplates, } from '../../templates/index.js';
|
|
6
7
|
let viteConfig;
|
|
8
|
+
let staticImports = [];
|
|
7
9
|
export default function index() {
|
|
8
10
|
return {
|
|
9
11
|
name: 'zuby-context-plugin',
|
|
@@ -17,7 +19,8 @@ export default function index() {
|
|
|
17
19
|
return;
|
|
18
20
|
}
|
|
19
21
|
const contextCode = await generateCompileTimeContextCode(ssr);
|
|
20
|
-
|
|
22
|
+
const staticImportsCode = getStaticImportsCode();
|
|
23
|
+
return staticImportsCode + contextCode + code;
|
|
21
24
|
},
|
|
22
25
|
};
|
|
23
26
|
}
|
|
@@ -42,6 +45,7 @@ export async function generateTemplatesCode(ssr) {
|
|
|
42
45
|
const innerLayouts = await getInnerLayouts(templates);
|
|
43
46
|
const errors = await getErrors(templates);
|
|
44
47
|
const handlers = await getHandlers(templates);
|
|
48
|
+
const loaders = await getLoaders(templates);
|
|
45
49
|
const pagesCode = await Promise.all(pages.map(generateTemplateCode) || []);
|
|
46
50
|
const appsCode = await Promise.all(apps.map(generateTemplateCode) || []);
|
|
47
51
|
const errorsCode = await Promise.all(errors.map(generateTemplateCode) || []);
|
|
@@ -50,6 +54,7 @@ export async function generateTemplatesCode(ssr) {
|
|
|
50
54
|
? await Promise.all(innerLayouts.map(generateTemplateCode) || [])
|
|
51
55
|
: [];
|
|
52
56
|
const handlersCode = ssr ? await Promise.all(handlers.map(generateTemplateCode) || []) : [];
|
|
57
|
+
const loadersCode = await Promise.all(loaders.map(generateTemplateCode) || []);
|
|
53
58
|
return `{
|
|
54
59
|
pages: [${pagesCode.join(',')}],
|
|
55
60
|
apps: [${appsCode.join(',')}],
|
|
@@ -57,6 +62,7 @@ export async function generateTemplatesCode(ssr) {
|
|
|
57
62
|
layouts: [${layoutsCode.join(',')}],
|
|
58
63
|
innerLayouts: [${innerLayoutsCode.join(',')}],
|
|
59
64
|
handlers: [${handlersCode.join(',')}],
|
|
65
|
+
loaders: [${loadersCode.join(',')}],
|
|
60
66
|
}`;
|
|
61
67
|
}
|
|
62
68
|
export async function generateTemplateCode(template) {
|
|
@@ -67,12 +73,28 @@ export async function generateTemplateCode(template) {
|
|
|
67
73
|
pathParams: ${JSON.stringify(template.pathParams)},
|
|
68
74
|
pathType: "${template.pathType}",
|
|
69
75
|
templateType: "${template.templateType}",
|
|
70
|
-
component: () =>
|
|
76
|
+
component: () => ${generateImportCode(template)},
|
|
71
77
|
}`;
|
|
72
78
|
}
|
|
79
|
+
export function generateImportCode(template) {
|
|
80
|
+
// Sync templates are imported statically
|
|
81
|
+
if (Object.values(SYNC_TEMPLATES).includes(template.templateType)) {
|
|
82
|
+
const key = `__zuby_static_import_${staticImports.length}`;
|
|
83
|
+
staticImports.push({
|
|
84
|
+
key,
|
|
85
|
+
path: template.filename,
|
|
86
|
+
});
|
|
87
|
+
return key;
|
|
88
|
+
}
|
|
89
|
+
// Async templates are imported dynamically
|
|
90
|
+
return `import("${template.filename}")`;
|
|
91
|
+
}
|
|
73
92
|
export async function generateRenderCode(ssr) {
|
|
74
93
|
const { jsx } = await getZubyInternalConfig();
|
|
75
94
|
if (!ssr)
|
|
76
95
|
return '{}';
|
|
77
96
|
return `await import("${jsx.renderFile}")`;
|
|
78
97
|
}
|
|
98
|
+
export function getStaticImportsCode() {
|
|
99
|
+
return staticImports.map(({ key, path }) => `import ${key} from "${path}";`).join('\n');
|
|
100
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { writeFileSync } from 'fs';
|
|
2
|
+
import { join, relative } from 'path';
|
|
3
|
+
import { normalizePath } from '../../utils/pathUtils.js';
|
|
4
|
+
import { getPages } from '../../templates/index.js';
|
|
5
|
+
import { PREALOD_MANIFEST } from '../../constants.js';
|
|
6
|
+
/**
|
|
7
|
+
* This is internal plugin
|
|
8
|
+
* that generates preload manifest file
|
|
9
|
+
* that is used for preloading assets.
|
|
10
|
+
*/
|
|
11
|
+
export default function preloadPlugin() {
|
|
12
|
+
return {
|
|
13
|
+
name: 'zuby-preload-plugin',
|
|
14
|
+
hooks: {
|
|
15
|
+
'zuby:build:done': async ({ config, clientChunksManifest, templates }) => {
|
|
16
|
+
const { srcDir, outDir } = config;
|
|
17
|
+
const preloadManifest = {};
|
|
18
|
+
const pages = await getPages(templates);
|
|
19
|
+
pages.forEach(page => {
|
|
20
|
+
const filename = normalizePath(relative(srcDir, page.filename));
|
|
21
|
+
preloadManifest[filename] = clientChunksManifest[filename] || [];
|
|
22
|
+
});
|
|
23
|
+
writeFileSync(normalizePath(join(outDir, 'client', PREALOD_MANIFEST)), JSON.stringify(preloadManifest, null, 2));
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
}
|
|
@@ -9,7 +9,7 @@ import { OUTPUTS } from '../../types.js';
|
|
|
9
9
|
import { PATH_TYPES } from '../../templates/types.js';
|
|
10
10
|
import { substitutePathParams } from '../../templates/pathUtils.js';
|
|
11
11
|
import { normalizePath } from '../../utils/pathUtils.js';
|
|
12
|
-
import { HTTP_HEADERS } from '../../constants.js';
|
|
12
|
+
import { HTTP_HEADERS, ZUBY_USER_AGENTS } from '../../constants.js';
|
|
13
13
|
/**
|
|
14
14
|
* This is internal plugin
|
|
15
15
|
* that pre-renders the pages during the build.
|
|
@@ -77,7 +77,7 @@ export default function prerenderPlugin() {
|
|
|
77
77
|
const reqBaseUrl = `http://${site || 'localhost'}`;
|
|
78
78
|
const reqOptions = {
|
|
79
79
|
headers: {
|
|
80
|
-
[HTTP_HEADERS.UserAgent]:
|
|
80
|
+
[HTTP_HEADERS.UserAgent]: ZUBY_USER_AGENTS.Prerenderer,
|
|
81
81
|
},
|
|
82
82
|
};
|
|
83
83
|
// Assign all discovered prerender paths to the config for other plugins.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface PreloadEntry {
|
|
2
|
+
href: string;
|
|
3
|
+
as: string;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Preloads given link with the lowest priority
|
|
7
|
+
* in the future when network is idle and DOM loaded.
|
|
8
|
+
* @example preload("/api/products/1", "json")
|
|
9
|
+
*/
|
|
10
|
+
export declare function preload(href: string, as?: string): void;
|
|
11
|
+
/**
|
|
12
|
+
* Preloads all required assets for matching page.
|
|
13
|
+
* Such as scripts, styles, props, etc.
|
|
14
|
+
* @example preloadPage("/products/1")
|
|
15
|
+
*/
|
|
16
|
+
export declare function preloadPage(href: string, onHandle?: () => void | Promise<void>): void;
|
package/preload/index.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { getContext } from '../context/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* The set of links that were already preloaded.
|
|
4
|
+
*/
|
|
5
|
+
const preloadLinks = new Set();
|
|
6
|
+
/**
|
|
7
|
+
* Preload manifest that lists all assets
|
|
8
|
+
* that should be preloaded for each page.
|
|
9
|
+
* It is generated by preloadPlugin.
|
|
10
|
+
* @example {
|
|
11
|
+
* "pages/index.tsx": [
|
|
12
|
+
* "/chunks/chunk-NGLgOfVh.js",
|
|
13
|
+
* "/chunks/chunk-8kxF91T_.css"
|
|
14
|
+
* ],
|
|
15
|
+
* }
|
|
16
|
+
*/
|
|
17
|
+
let preloadManifest;
|
|
18
|
+
/**
|
|
19
|
+
* Preloads given link with the lowest priority
|
|
20
|
+
* in the future when network is idle and DOM loaded.
|
|
21
|
+
* @example preload("/api/products/1", "json")
|
|
22
|
+
*/
|
|
23
|
+
export function preload(href, as = 'fetch') {
|
|
24
|
+
// Do nothing on server
|
|
25
|
+
if (typeof window === 'undefined')
|
|
26
|
+
return;
|
|
27
|
+
// Preload link only once
|
|
28
|
+
if (preloadLinks.has(href))
|
|
29
|
+
return;
|
|
30
|
+
preloadLinks.add(href);
|
|
31
|
+
// Detect asset type by extension
|
|
32
|
+
as = href.match(/\.css(\?.*)?$/) ? 'style' : as;
|
|
33
|
+
as = href.match(/\.js(\?.*)?$/) ? 'script' : as;
|
|
34
|
+
// Preload link in the future when network is idle
|
|
35
|
+
// and DOM content was loaded.
|
|
36
|
+
window.requestIdleCallback(() => addPreloadEntry({
|
|
37
|
+
href,
|
|
38
|
+
as,
|
|
39
|
+
}));
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Preloads all required assets for matching page.
|
|
43
|
+
* Such as scripts, styles, props, etc.
|
|
44
|
+
* @example preloadPage("/products/1")
|
|
45
|
+
*/
|
|
46
|
+
export function preloadPage(href, onHandle = () => { }) {
|
|
47
|
+
// Do nothing on server
|
|
48
|
+
if (typeof window === 'undefined')
|
|
49
|
+
return;
|
|
50
|
+
const context = getContext();
|
|
51
|
+
const pages = context.templates?.pages || [];
|
|
52
|
+
const page = pages.find(({ pathRegex }) => {
|
|
53
|
+
return pathRegex.test(href);
|
|
54
|
+
});
|
|
55
|
+
// Preload link itself
|
|
56
|
+
// if matching page was not found
|
|
57
|
+
if (!page) {
|
|
58
|
+
preload(href);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
window.requestIdleCallback(async () => {
|
|
62
|
+
await loadPreloadManifest();
|
|
63
|
+
// Preload assets such as scripts and styles
|
|
64
|
+
const preloadAssets = preloadManifest?.[page.filename] || [];
|
|
65
|
+
preloadAssets.forEach(href => preload(href));
|
|
66
|
+
onHandle();
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Appends preload link into head element of page.
|
|
71
|
+
* For example:
|
|
72
|
+
* <link rel="preload" href="/api/products/1" as="json"/>
|
|
73
|
+
*/
|
|
74
|
+
function addPreloadEntry({ href, as }) {
|
|
75
|
+
const preloadLink = document.createElement('link');
|
|
76
|
+
preloadLink.href = href;
|
|
77
|
+
preloadLink.as = as;
|
|
78
|
+
preloadLink.rel = 'preload';
|
|
79
|
+
document.head.appendChild(preloadLink);
|
|
80
|
+
}
|
|
81
|
+
async function loadPreloadManifest() {
|
|
82
|
+
if (preloadManifest)
|
|
83
|
+
return;
|
|
84
|
+
preloadManifest = {};
|
|
85
|
+
const res = await fetch('/preload-manifest.json');
|
|
86
|
+
preloadManifest = await res.json();
|
|
87
|
+
}
|
package/server/index.js
CHANGED
|
@@ -875,7 +875,7 @@ function normalizePath(path2) {
|
|
|
875
875
|
}
|
|
876
876
|
|
|
877
877
|
// src/server/zubyServer.ts
|
|
878
|
-
import { resolve as resolve3 } from "path";
|
|
878
|
+
import { resolve as resolve3, basename as basename2 } from "path";
|
|
879
879
|
import { createReadStream, existsSync as existsSync2, statSync } from "fs";
|
|
880
880
|
|
|
881
881
|
// src/server/mimeTypes.ts
|
|
@@ -2319,6 +2319,13 @@ var ZubyPageContext = class {
|
|
|
2319
2319
|
return path2;
|
|
2320
2320
|
return `/${locale}/${path2}`.replace(/\/+/g, "/");
|
|
2321
2321
|
}
|
|
2322
|
+
/**
|
|
2323
|
+
* Returns true if the current request
|
|
2324
|
+
* was made by the Zuby.js pre-render build step.
|
|
2325
|
+
*/
|
|
2326
|
+
get isPrerendering() {
|
|
2327
|
+
return this._request?.headers?.get("user-agent") === "zuby-prerender";
|
|
2328
|
+
}
|
|
2322
2329
|
};
|
|
2323
2330
|
|
|
2324
2331
|
// src/server/zubyRenderer.ts
|
|
@@ -8507,7 +8514,8 @@ var BASE_TEMPLATES = {
|
|
|
8507
8514
|
innerLayout: "innerLayout",
|
|
8508
8515
|
app: "app",
|
|
8509
8516
|
error: "error",
|
|
8510
|
-
entry: "entry"
|
|
8517
|
+
entry: "entry",
|
|
8518
|
+
loader: "loader"
|
|
8511
8519
|
};
|
|
8512
8520
|
var TEMPLATES = {
|
|
8513
8521
|
...BASE_TEMPLATES,
|
|
@@ -8812,6 +8820,9 @@ var ZubyServer = class {
|
|
|
8812
8820
|
"Content-Length": fileStats.size.toString(),
|
|
8813
8821
|
"Last-Modified": fileStats.mtime.toUTCString()
|
|
8814
8822
|
};
|
|
8823
|
+
if (basename2(file).match(/^(chunk|entry)-/)) {
|
|
8824
|
+
headers["Cache-Control"] = "public, max-age=31536000, immutable";
|
|
8825
|
+
}
|
|
8815
8826
|
let streamOptions = {
|
|
8816
8827
|
start: 0,
|
|
8817
8828
|
end: fileStats.size - 1
|
package/server/zubyServer.js
CHANGED
|
@@ -2,7 +2,7 @@ import { createServer as createHttpsServer } from 'node:https';
|
|
|
2
2
|
import { createServer as createHttpServer } from 'node:http';
|
|
3
3
|
import { readFileSync } from 'node:fs';
|
|
4
4
|
import { normalizePath } from '../utils/pathUtils.js';
|
|
5
|
-
import { resolve } from 'path';
|
|
5
|
+
import { resolve, basename } from 'path';
|
|
6
6
|
import { createReadStream, existsSync, statSync } from 'fs';
|
|
7
7
|
import { mimeTypes } from './mimeTypes.js';
|
|
8
8
|
import { createLogger } from '../logger/index.js';
|
|
@@ -76,6 +76,11 @@ export default class ZubyServer {
|
|
|
76
76
|
'Content-Length': fileStats.size.toString(),
|
|
77
77
|
'Last-Modified': fileStats.mtime.toUTCString(),
|
|
78
78
|
};
|
|
79
|
+
// Chunks and entry can be cached forever,
|
|
80
|
+
// because they have a hash in their filename
|
|
81
|
+
if (basename(file).match(/^(chunk|entry)-/)) {
|
|
82
|
+
headers['Cache-Control'] = 'public, max-age=31536000, immutable';
|
|
83
|
+
}
|
|
79
84
|
let streamOptions = {
|
|
80
85
|
start: 0,
|
|
81
86
|
end: fileStats.size - 1,
|
package/templates/index.d.ts
CHANGED
|
@@ -23,6 +23,10 @@ export declare function getInnerLayouts(templates?: Template[]): Promise<Templat
|
|
|
23
23
|
* Returns the array of app templates.
|
|
24
24
|
*/
|
|
25
25
|
export declare function getApps(templates?: Template[]): Promise<Template[]>;
|
|
26
|
+
/**
|
|
27
|
+
* Returns the array of Loader templates.
|
|
28
|
+
*/
|
|
29
|
+
export declare function getLoaders(templates?: Template[]): Promise<Template[]>;
|
|
26
30
|
/**
|
|
27
31
|
* Returns the array of page templates.
|
|
28
32
|
*/
|
package/templates/index.js
CHANGED
|
@@ -55,6 +55,14 @@ export async function getApps(templates) {
|
|
|
55
55
|
const apps = await getTemplatesOfType(TEMPLATES.app, templates);
|
|
56
56
|
return [...apps, getDefaultTemplate(jsx.appTemplateFile, TEMPLATES.app)];
|
|
57
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* Returns the array of Loader templates.
|
|
60
|
+
*/
|
|
61
|
+
export async function getLoaders(templates) {
|
|
62
|
+
const { jsx } = await getZubyInternalConfig();
|
|
63
|
+
const loaders = await getTemplatesOfType(TEMPLATES.loader, templates);
|
|
64
|
+
return [...loaders, getDefaultTemplate(jsx.loaderTemplateFile, TEMPLATES.loader)];
|
|
65
|
+
}
|
|
58
66
|
/**
|
|
59
67
|
* Returns the array of page templates.
|
|
60
68
|
*/
|
package/templates/types.d.ts
CHANGED
|
@@ -30,6 +30,7 @@ export declare const BASE_TEMPLATES: {
|
|
|
30
30
|
app: string;
|
|
31
31
|
error: string;
|
|
32
32
|
entry: string;
|
|
33
|
+
loader: string;
|
|
33
34
|
};
|
|
34
35
|
export declare const TEMPLATES: {
|
|
35
36
|
page: string;
|
|
@@ -40,5 +41,9 @@ export declare const TEMPLATES: {
|
|
|
40
41
|
app: string;
|
|
41
42
|
error: string;
|
|
42
43
|
entry: string;
|
|
44
|
+
loader: string;
|
|
45
|
+
};
|
|
46
|
+
export declare const SYNC_TEMPLATES: {
|
|
47
|
+
loader: string;
|
|
43
48
|
};
|
|
44
49
|
export type TemplateType = (typeof TEMPLATES)[keyof typeof TEMPLATES];
|
package/templates/types.js
CHANGED
|
@@ -9,6 +9,7 @@ export const BASE_TEMPLATES = {
|
|
|
9
9
|
app: 'app',
|
|
10
10
|
error: 'error',
|
|
11
11
|
entry: 'entry',
|
|
12
|
+
loader: 'loader',
|
|
12
13
|
};
|
|
13
14
|
export const TEMPLATES = {
|
|
14
15
|
...BASE_TEMPLATES,
|
|
@@ -16,3 +17,6 @@ export const TEMPLATES = {
|
|
|
16
17
|
handler: 'handler',
|
|
17
18
|
markdown: 'markdown',
|
|
18
19
|
};
|
|
20
|
+
export const SYNC_TEMPLATES = {
|
|
21
|
+
loader: 'loader',
|
|
22
|
+
};
|
package/types.d.ts
CHANGED
|
@@ -164,10 +164,34 @@ export interface BuildCommandOptions extends BaseCommandOptions {
|
|
|
164
164
|
}
|
|
165
165
|
export interface InitCommandOptions {
|
|
166
166
|
/**
|
|
167
|
-
* The
|
|
168
|
-
* @
|
|
167
|
+
* The relative path to the project directory.
|
|
168
|
+
* @example './my-zuby-app'
|
|
169
169
|
*/
|
|
170
|
-
|
|
170
|
+
projectPath?: string;
|
|
171
|
+
/**
|
|
172
|
+
* The name of the JSX provider,
|
|
173
|
+
* a.k.a. the framework you want to use to write your components.
|
|
174
|
+
* @example 'preact'
|
|
175
|
+
*/
|
|
176
|
+
jsxProviderName?: string;
|
|
177
|
+
/**
|
|
178
|
+
* The name of the example project to use.
|
|
179
|
+
* @example 'basic'
|
|
180
|
+
*/
|
|
181
|
+
exampleName?: string;
|
|
182
|
+
/**
|
|
183
|
+
* Set this option to true to use TypeScript
|
|
184
|
+
* for your new project.
|
|
185
|
+
* @example true
|
|
186
|
+
*/
|
|
187
|
+
useTypescript?: boolean;
|
|
188
|
+
}
|
|
189
|
+
export interface UpgradeCommandOptions extends BaseCommandOptions {
|
|
190
|
+
/**
|
|
191
|
+
* The version tag to upgrade to.
|
|
192
|
+
* Defaults to the latest compatible version.
|
|
193
|
+
*/
|
|
194
|
+
tag?: string;
|
|
171
195
|
}
|
|
172
196
|
export declare const MODES: {
|
|
173
197
|
development: string;
|
|
@@ -188,6 +212,7 @@ export interface JsxProvider {
|
|
|
188
212
|
layoutTemplateFile: string;
|
|
189
213
|
innerLayoutTemplateFile: string;
|
|
190
214
|
errorTemplateFile: string;
|
|
215
|
+
loaderTemplateFile: string;
|
|
191
216
|
}
|
|
192
217
|
export type RenderToString = (vnode: any) => Promise<string> | string;
|
|
193
218
|
export type RenderToStream = (vnode: any) => Promise<ReadableStream> | ReadableStream;
|