Skip to content

Commit

Permalink
fix: SSR HTML entrypoints in dev mode (#85)
Browse files Browse the repository at this point in the history
  • Loading branch information
aklinker1 authored Mar 16, 2023
1 parent d0226ef commit 1080610
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 106 deletions.
4 changes: 2 additions & 2 deletions packages/demo-vue/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "pnpm i && pnpm vite",
"dev:firefox": "pnpm i && pnpm TARGET=firefox vite",
"dev": "pnpm i && vite",
"dev:firefox": "pnpm i && TARGET=firefox vite",
"build": "pnpm i && tsc && vite build",
"build:firefox": "pnpm i && tsc && TARGET=firefox vite build"
},
Expand Down
7 changes: 3 additions & 4 deletions packages/vite-plugin-web-extension/src/build/build-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,14 @@ import { BOLD, DIM, Logger, RESET, GREEN } from "../logger";
import { createMultibuildCompleteManager } from "../plugins/multibuild-complete-plugin";
import { bundleTrackerPlugin } from "../plugins/bundle-tracker-plugin";
import { getViteConfigsForInputs } from "./getViteConfigsForInputs";
import { hmrRewritePlugin } from "../plugins/hmr-rewrite-plugin";
import { BundleMap } from "./renderManifest";

interface RebuildOptions {
paths: ProjectPaths;
userConfig: vite.UserConfig;
resolvedConfig: vite.ResolvedConfig;
manifest: any;
mode: BuildMode;
server?: vite.ViteDevServer;
onSuccess?: () => Promise<void> | void;
}

Expand Down Expand Up @@ -52,8 +51,8 @@ export function createBuildContext({
async function getBuildConfigs({
paths,
userConfig,
resolvedConfig,
manifest,
server,
onSuccess,
mode,
}: RebuildOptions) {
Expand All @@ -62,7 +61,7 @@ export function createBuildContext({
manifest,
mode,
logger,
resolvedConfig,
server,
additionalInputs: pluginOptions.additionalInputs,
baseHtmlViteConfig: pluginOptions.htmlViteConfig ?? {},
baseSandboxViteConfig: {},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,14 +73,13 @@ export function getViteConfigsForInputs(options: {
additionalInputs: string[];
manifest: Manifest;
logger: Logger;
resolvedConfig: vite.ResolvedConfig;
server?: vite.ViteDevServer;
baseHtmlViteConfig: vite.InlineConfig;
baseSandboxViteConfig: vite.InlineConfig;
baseScriptViteConfig: vite.InlineConfig;
baseOtherViteConfig: vite.InlineConfig;
}): CombinedViteConfigs {
const { paths, additionalInputs, manifest, mode, logger, resolvedConfig } =
options;
const { paths, additionalInputs, manifest, mode, logger, server } = options;
const configs = new CombinedViteConfigs();

const processedInputs = new Set<string>();
Expand All @@ -103,11 +102,7 @@ export function getViteConfigsForInputs(options: {
mode === BuildMode.DEV
? [
hmrRewritePlugin({
server: resolvedConfig.server,
hmr:
typeof resolvedConfig.server.hmr === "object"
? resolvedConfig.server.hmr
: undefined,
server: server!,
paths,
logger,
}),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
/**
* This plugin is responsible for rewriting the entry HTML files to point towards the dev server.
*/
import {
HmrOptions,
InlineConfig,
mergeConfig,
Plugin,
ServerOptions,
} from "vite";
import * as vite from "vite";
import { HMR_REWRITE_PLUGIN_NAME } from "../constants";
import { parseHTML } from "linkedom";
import path from "path";
Expand All @@ -16,20 +10,19 @@ import { Logger } from "../logger";
import { inspect } from "util";

export function hmrRewritePlugin(config: {
/**
* The resolved config.server options for the dev server vite build
*/
server: ServerOptions;
/**
* The resolved config.server.hmr options for the dev server vite build
*/
hmr: HmrOptions | undefined;
server: vite.ViteDevServer;
paths: ProjectPaths;
logger: Logger;
}): Plugin {
const { hmr, server, paths, logger } = config;
}): vite.Plugin {
const { paths, logger, server } = config;
let inputIds: string[] = [];

const serverOptions = server.config.server;
let hmrOptions =
typeof server.config.server.hmr === "object"
? server.config.server.hmr
: undefined;

// Coped from node_modules/vite, do a global search for: vite:client-inject
function serializeDefine(define: any): string {
let res = `{`;
Expand All @@ -48,7 +41,7 @@ export function hmrRewritePlugin(config: {
config(config) {
inputIds = Object.values(config.build?.rollupOptions?.input ?? {});

const hmrConfig: InlineConfig = {
const hmrConfig: vite.InlineConfig = {
server: {
hmr: {
protocol: "http:",
Expand All @@ -61,32 +54,34 @@ export function hmrRewritePlugin(config: {
// These are used in node_modules/vite/dist/client/client.mjs, check there to see if a var
// can be null or not.
__MODE__: JSON.stringify(config.mode || null),
__BASE__: JSON.stringify(server.base || "/"),
__BASE__: JSON.stringify(serverOptions.base || "/"),
__DEFINES__: serializeDefine(config.define || {}),
__SERVER_HOST__: JSON.stringify(server.host || "localhost"),
__HMR_PROTOCOL__: JSON.stringify(hmr?.protocol || null),
__HMR_HOSTNAME__: JSON.stringify(hmr?.host || "localhost"),
__HMR_PORT__: JSON.stringify(hmr?.clientPort || hmr?.port || 5173),
__SERVER_HOST__: JSON.stringify(serverOptions.host || "localhost"),
__HMR_PROTOCOL__: JSON.stringify(hmrOptions?.protocol || null),
__HMR_HOSTNAME__: JSON.stringify(hmrOptions?.host || "localhost"),
__HMR_PORT__: JSON.stringify(
hmrOptions?.clientPort || hmrOptions?.port || 5173
),
__HMR_DIRECT_TARGET__: JSON.stringify(
`${server.host ?? "localhost"}:${server.port ?? 5173}${
config.base || "/"
}`
`${serverOptions.host ?? "localhost"}:${
serverOptions.port ?? 5173
}${config.base || "/"}`
),
__HMR_BASE__: JSON.stringify(server.base ?? "/"),
__HMR_TIMEOUT__: JSON.stringify(hmr?.timeout || 30000),
__HMR_ENABLE_OVERLAY__: JSON.stringify(hmr?.overlay !== false),
__HMR_BASE__: JSON.stringify(serverOptions.base ?? "/"),
__HMR_TIMEOUT__: JSON.stringify(hmrOptions?.timeout || 30000),
__HMR_ENABLE_OVERLAY__: JSON.stringify(hmrOptions?.overlay !== false),
},
};
return mergeConfig(config, hmrConfig);
return vite.mergeConfig(config, hmrConfig);
},
transform(code, id) {
async transform(code, id) {
// Only transform HTML inputs
if (!id.endsWith(".html") || !inputIds.includes(id)) return;

const baseUrl = "http://localhost:5173";

// Load scripts from dev server
const { document } = parseHTML(code);
const { document } = parseHTML(await server.transformIndexHtml(id, code));

const pointToDevServer = (querySelector: string, attr: string): void => {
document.querySelectorAll(querySelector).forEach((element) => {
Expand Down Expand Up @@ -115,12 +110,6 @@ export function hmrRewritePlugin(config: {
pointToDevServer("script[type=module]", "src");
pointToDevServer("link[rel=stylesheet]", "href");

// Add vite client to page
const clientScript = document.createElement("script");
clientScript.type = "module";
clientScript.src = `${baseUrl}/@vite/client`;
document.head.append(clientScript);

// Return new HTML
return document.toString();
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as vite from "vite";
import type * as rollup from "rollup";
import { ResolvedOptions, Manifest, ProjectPaths } from "../options";
import { createLogger } from "../logger";
import { MANIFEST_LOADER_PLUGIN_NAME } from "../constants";
Expand Down Expand Up @@ -96,6 +97,53 @@ export function manifestLoaderPlugin(options: ResolvedOptions): vite.Plugin {
logger.log("Done!");
}

async function buildExtension({
emitFile,
server,
}: {
emitFile: (asset: rollup.EmittedAsset) => void | Promise<void>;
server?: vite.ViteDevServer;
}) {
// Build
const manifestWithInputs = await loadManifest();
await ctx.rebuild({
paths,
userConfig,
manifest: manifestWithInputs,
mode,
server,
onSuccess: async () => {
if (extensionRunner) await extensionRunner.reload();
},
});

// Generate the manifest based on the output files
const finalManifest = renderManifest(manifestWithInputs, ctx.getBundles());

// Add permissions and CSP for the dev server
if (mode === BuildMode.DEV) {
applyDevServerCsp(finalManifest);
}

if (!options.skipManifestValidation) {
await validateManifest(finalManifest);
}
emitFile({
type: "asset",
source: JSON.stringify(finalManifest),
fileName: "manifest.json",
name: "manifest.json",
});

await copyPublicDirToOutDir({ mode, paths });

// In dev mode, open up the browser immediately after the build context is finished with the
// first build.
if (mode === BuildMode.DEV) {
await openBrowser();
}
}

return {
name: MANIFEST_LOADER_PLUGIN_NAME,

Expand Down Expand Up @@ -135,6 +183,28 @@ export function manifestLoaderPlugin(options: ResolvedOptions): vite.Plugin {
};
},

configureServer(server) {
server.httpServer?.on("listening", () => {
// In dev mode, the files have to be built AFTER the server is started so the HTML files can
// be SSR-ed so they have the correct contents.
if (mode === BuildMode.DEV) {
buildExtension({
server,
async emitFile(asset) {
logger.log(
"\nWriting \x1b[95mmanifest.json\x1b[0m before opening extension in browser..."
);
await fs.writeFile(
path.resolve(paths.outDir, asset.fileName ?? "unknown"),
asset.source ?? "{}",
"utf8"
);
},
});
}
});
},

// Runs during: Build, dev, watch
async buildStart() {
// Empty out directory
Expand All @@ -152,57 +222,11 @@ export function manifestLoaderPlugin(options: ResolvedOptions): vite.Plugin {
this.addWatchFile(path.resolve(paths.rootDir, options.manifest));
}

// Build
const manifestWithInputs = await loadManifest();
await ctx.rebuild({
paths,
userConfig,
resolvedConfig,
manifest: manifestWithInputs,
mode,
onSuccess: async () => {
if (extensionRunner) await extensionRunner.reload();
},
});

// Generate the manifest based on the output files
const finalManifest = renderManifest(
manifestWithInputs,
ctx.getBundles()
);

// Add permissions and CSP for the dev server
if (mode === BuildMode.DEV) {
applyDevServerCsp(finalManifest);
}

if (!options.skipManifestValidation) {
await validateManifest(finalManifest);
}
// This is where we build the extension in build and watch mode.
if (mode !== BuildMode.DEV) {
this.emitFile({
type: "asset",
source: JSON.stringify(finalManifest),
fileName: "manifest.json",
name: "manifest.json",
await buildExtension({
emitFile: (asset) => void this.emitFile(asset),
});
} else {
logger.log(
"\nWriting \x1b[95mmanifest.json\x1b[0m before starting dev server..."
);
await fs.writeFile(
path.resolve(paths.outDir, "manifest.json"),
JSON.stringify(finalManifest),
"utf8"
);
}

await copyPublicDirToOutDir({ mode, paths });

// In dev mode, open up the browser immediately after the build context is finished with the
// first build.
if (mode === BuildMode.DEV) {
await openBrowser();
}
},

Expand Down Expand Up @@ -275,6 +299,7 @@ async function copyPublicDirToOutDir({

async function applyDevServerCsp(manifest: Manifest) {
manifest.permissions ??= [];
// TODO: Only add if permission isn't already present
manifest.permissions.push("http://localhost/*");

const csp = new ContentSecurityPolicy(
Expand Down
6 changes: 1 addition & 5 deletions pnpm-lock.yaml

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

0 comments on commit 1080610

Please sign in to comment.