Skip to content

Commit

Permalink
feat: vercel adaptor (QwikDev#2088)
Browse files Browse the repository at this point in the history
  • Loading branch information
adamdbradley authored Nov 11, 2022
1 parent 846d938 commit 9c50e1f
Show file tree
Hide file tree
Showing 32 changed files with 725 additions and 564 deletions.
18 changes: 2 additions & 16 deletions packages/qwik-city/adaptors/cloudflare-pages/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@
```ts

import type { QwikManifest } from '@builder.io/qwik/optimizer';
import type { SymbolMapper } from '@builder.io/qwik/optimizer';
import type { SymbolMapperFn } from '@builder.io/qwik/optimizer';
import type { StaticGenerateRenderOptions } from '@builder.io/qwik-city/static';

// @alpha (undocumented)
export function cloudflarePagesAdaptor(opts?: CloudflarePagesAdaptorOptions): any;
Expand All @@ -17,19 +15,7 @@ export interface CloudflarePagesAdaptorOptions {
staticGenerate?: StaticGenerateRenderOptions | true;
}

// Warning: (ae-forgotten-export) The symbol "RenderOptions" needs to be exported by the entry point index.d.ts
//
// @alpha (undocumented)
export interface StaticGenerateRenderOptions extends RenderOptions {
emitData?: boolean;
emitHtml?: boolean;
log?: 'debug';
maxTasksPerWorker?: number;
maxWorkers?: number;
origin: string;
outDir: string;
sitemapOutFile?: string;
}
export { StaticGenerateRenderOptions }

// (No @packageDocumentation comment for this package)

Expand Down
204 changes: 58 additions & 146 deletions packages/qwik-city/adaptors/cloudflare-pages/vite/index.ts
Original file line number Diff line number Diff line change
@@ -1,172 +1,84 @@
import type { Plugin } from 'vite';
import type { PluginContext } from 'rollup';
import type { QwikCityPlugin } from '@builder.io/qwik-city/vite';
import type { QwikVitePlugin } from '@builder.io/qwik/optimizer';
import type { StaticGenerateOptions, StaticGenerateRenderOptions } from '../../../static';
import { join } from 'node:path';
import type { StaticGenerateRenderOptions } from '@builder.io/qwik-city/static';
import { viteAdaptor } from '../../shared/vite';
import fs from 'node:fs';
import { join } from 'node:path';

/**
* @alpha
*/
export function cloudflarePagesAdaptor(opts: CloudflarePagesAdaptorOptions = {}): any {
let qwikCityPlugin: QwikCityPlugin | null = null;
let qwikVitePlugin: QwikVitePlugin | null = null;
let serverOutDir: string | null = null;
let renderModulePath: string | null = null;
let qwikCityPlanModulePath: string | null = null;

async function generateBundles(ctx: PluginContext) {
const qwikVitePluginApi = qwikVitePlugin!.api;
const clientOutDir = qwikVitePluginApi.getClientOutDir()!;
const files = await fs.promises.readdir(clientOutDir, { withFileTypes: true });
const exclude = files
.map((file) => {
if (file.name.startsWith('.')) {
return null;
}
if (file.isDirectory()) {
return `/${file.name}/*`;
} else if (file.isFile()) {
return `/${file.name}`;
}
return null;
})
.filter(isNotNullable);
const include: string[] = ['/*'];

const serverPackageJsonPath = join(serverOutDir!, 'package.json');
const serverPackageJsonCode = `{"type":"module"}`;
await fs.promises.mkdir(serverOutDir!, { recursive: true });
await fs.promises.writeFile(serverPackageJsonPath, serverPackageJsonCode);

if (opts.staticGenerate && renderModulePath && qwikCityPlanModulePath) {
const staticGenerate = await import('../../../static');
let generateOpts: StaticGenerateOptions = {
outDir: clientOutDir,
origin: process?.env?.CF_PAGES_URL || 'https://your.cloudflare.pages.dev',
renderModulePath: renderModulePath,
qwikCityPlanModulePath: qwikCityPlanModulePath,
basePathname: qwikCityPlugin!.api.getBasePathname(),
};

if (typeof opts.staticGenerate === 'object') {
generateOpts = {
...generateOpts,
...opts.staticGenerate,
};
}
const results = await staticGenerate.generate(generateOpts);
results.staticPaths.sort();
results.staticPaths.sort((a, b) => {
return a.length - b.length;
});
if (results.errors > 0) {
ctx.error('Error while runnning SSG. At least one path failed to render.');
}
exclude.push(...results.staticPaths);
}

const hasRoutesJson = exclude.includes('/_routes.json');
if (!hasRoutesJson && opts.functionRoutes !== false) {
const routesJsonPath = join(clientOutDir, '_routes.json');
const total = include.length + exclude.length;
const maxRules = 100;
if (total > maxRules) {
const toRemove = total - maxRules;
const removed = exclude.splice(-toRemove, toRemove);
ctx.warn(
`Cloudflare pages does not support more than 100 static rules. Qwik SSG generated ${total}, the following rules were excluded: ${JSON.stringify(
removed,
undefined,
2
)}`
);
ctx.warn('Create an configure a "_routes.json" manually in the public.');
}

const routesJson = {
version: 1,
include,
exclude,
};
await fs.promises.writeFile(routesJsonPath, JSON.stringify(routesJson, undefined, 2));
}
}

const plugin: Plugin = {
name: 'vite-plugin-qwik-city-cloudflare-pages',
enforce: 'post',
apply: 'build',
return viteAdaptor({
name: 'cloudflare-pages',
origin: process?.env?.CF_PAGES_URL || 'https://your.cloudflare.pages.dev',
staticGenerate: opts.staticGenerate,

config() {
return {
ssr: {
target: 'webworker',
noExternal: true,
},
build: {
ssr: true,
rollupOptions: {
output: {
format: 'es',
hoistTransitiveImports: false,
},
},
},
ssr: {
target: 'webworker',
},
publicDir: false,
};
},

configResolved({ build, plugins }) {
qwikCityPlugin = plugins.find((p) => p.name === 'vite-plugin-qwik-city') as QwikCityPlugin;
if (!qwikCityPlugin) {
throw new Error('Missing vite-plugin-qwik-city');
}
qwikVitePlugin = plugins.find((p) => p.name === 'vite-plugin-qwik') as QwikVitePlugin;
if (!qwikVitePlugin) {
throw new Error('Missing vite-plugin-qwik');
}
serverOutDir = build.outDir;

if (build?.ssr !== true) {
throw new Error(
'"build.ssr" must be set to `true` in order to use the Cloudflare Pages adaptor.'
);
}

if (!build?.rollupOptions?.input) {
throw new Error(
'"build.rollupOptions.input" must be set in order to use the Cloudflare Pages adaptor.'
);
}
},

generateBundle(_, bundles) {
for (const fileName in bundles) {
const chunk = bundles[fileName];
if (chunk.type === 'chunk' && chunk.isEntry) {
if (chunk.name === 'entry.ssr') {
renderModulePath = join(serverOutDir!, fileName);
} else if (chunk.name === '@qwik-city-plan') {
qwikCityPlanModulePath = join(serverOutDir!, fileName);
async generateRoutes({ clientOutDir, staticPaths, warn }) {
const clientFiles = await fs.promises.readdir(clientOutDir, { withFileTypes: true });
const exclude = clientFiles
.map((f) => {
if (f.name.startsWith('.')) {
return null;
}
if (f.isDirectory()) {
return `/${f.name}/*`;
} else if (f.isFile()) {
return `/${f.name}`;
}
return null;
})
.filter(isNotNullable);
const include: string[] = ['/*'];

const hasRoutesJson = exclude.includes('/_routes.json');
if (!hasRoutesJson && opts.functionRoutes !== false) {
staticPaths.sort();
staticPaths.sort((a, b) => a.length - b.length);
exclude.push(...staticPaths);

const routesJsonPath = join(clientOutDir, '_routes.json');
const total = include.length + exclude.length;
const maxRules = 100;
if (total > maxRules) {
const toRemove = total - maxRules;
const removed = exclude.splice(-toRemove, toRemove);
warn(
`Cloudflare Pages does not support more than 100 static rules. Qwik SSG generated ${total}, the following rules were excluded: ${JSON.stringify(
removed,
undefined,
2
)}`
);
warn('Please manually create a routes config in the "public/_routes.json" directory.');
}
}

if (!renderModulePath) {
throw new Error(
'Unable to find "entry.ssr" entry point. Did you forget to add it to "build.rollupOptions.input"?'
);
}
if (!qwikCityPlanModulePath) {
throw new Error(
'Unable to find "@qwik-city-plan" entry point. Did you forget to add it to "build.rollupOptions.input"?'
);
const routesJson = {
version: 1,
include,
exclude,
};
await fs.promises.writeFile(routesJsonPath, JSON.stringify(routesJson, undefined, 2));
}
},

async closeBundle() {
await generateBundles(this);
},
};
return plugin;
});
}

/**
Expand All @@ -187,7 +99,7 @@ export interface CloudflarePagesAdaptorOptions {
staticGenerate?: StaticGenerateRenderOptions | true;
}

export type { StaticGenerateRenderOptions } from '../../../static';
export type { StaticGenerateRenderOptions };

const isNotNullable = <T>(v: T): v is NonNullable<T> => {
return v != null;
Expand Down
4 changes: 2 additions & 2 deletions packages/qwik-city/adaptors/express/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ import type { SymbolMapper } from '@builder.io/qwik/optimizer';
import type { SymbolMapperFn } from '@builder.io/qwik/optimizer';

// @alpha (undocumented)
export function expressAdaptor(opts?: NetlifyEdgeAdaptorOptions): any;
export function expressAdaptor(opts?: ExpressAdaptorOptions): any;

// @alpha (undocumented)
export interface NetlifyEdgeAdaptorOptions {
export interface ExpressAdaptorOptions {
// Warning: (ae-forgotten-export) The symbol "StaticGenerateRenderOptions" needs to be exported by the entry point index.d.ts
//
// (undocumented)
Expand Down
Loading

0 comments on commit 9c50e1f

Please sign in to comment.