Skip to content

Commit

Permalink
wip: working?
Browse files Browse the repository at this point in the history
  • Loading branch information
hi-ogawa committed Nov 14, 2024
1 parent 5131158 commit e67ffe3
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 14 deletions.
7 changes: 6 additions & 1 deletion viteroll/examples/ssr/src/entry-server.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import ReactDOMServer from "react-dom/server";
// @ts-ignore TODO: external require (e.g. require("stream")) not supported
import ReactDOMServer from "react-dom/server.browser";
import type { Connect } from "vite";
import { App } from "./app";

Expand All @@ -23,3 +24,7 @@ const handler: Connect.SimpleHandleFunction = (req, res) => {
};

export default handler;

if (typeof module !== "undefined") {
(module as any).hot.accept();
}
4 changes: 2 additions & 2 deletions viteroll/examples/ssr/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export default defineConfig({
plugins: [
viteroll({
reactRefresh: true,
// ssrPatchModule: true,
ssrPatchModule: true,
}),
{
name: "ssr-middleware",
Expand All @@ -41,7 +41,7 @@ export default defineConfig({
const devEnv = server.environments.ssr as RolldownEnvironment;
server.middlewares.use(async (req, res, next) => {
try {
const mod = (await devEnv.import("index")) as any;
const mod = (await devEnv.import("src/entry-server.tsx")) as any;
await mod.default(req, res);
} catch (e) {
next(e);
Expand Down
86 changes: 75 additions & 11 deletions viteroll/viteroll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ export class RolldownEnvironment extends DevEnvironment {
},
define: this.config.define,
plugins: [
viterollEntryPlugin(this.config, this.viterollOptions),
viterollEntryPlugin(this.config, this.viterollOptions, this),
// TODO: how to use jsx-dev-runtime?
rolldownExperimental.transformPlugin({
reactRefresh:
Expand All @@ -243,23 +243,24 @@ export class RolldownEnvironment extends DevEnvironment {
};
this.instance = await rolldown.rolldown(this.inputOptions);

// `generate` should work but we use `write` so it's easier to see output and debug
const format: rolldown.ModuleFormat =
this.name === "client" ||
(this.name === "ssr" && this.viterollOptions.ssrPatchModule)
? "app"
: "esm";
this.outputOptions = {
dir: this.outDir,
format:
this.name === "client" ||
(this.name === "ssr" && this.viterollOptions.ssrPatchModule)
? "app"
: "esm",
format,
// TODO: hmr_rebuild returns source map file when `sourcemap: true`
sourcemap: "inline",
// TODO: https://github.com/rolldown/rolldown/issues/2041
// handle `require("stream")` in `react-dom/server`
banner:
this.name === "ssr"
this.name === "ssr" && format === "esm"
? `import __nodeModule from "node:module"; const require = __nodeModule.createRequire(import.meta.url);`
: undefined,
};
// `generate` should work but we use `write` so it's easier to see output and debug
this.result = await this.instance.write(this.outputOptions);

this.buildTimestamp = Date.now();
Expand All @@ -276,7 +277,10 @@ export class RolldownEnvironment extends DevEnvironment {
}
if (this.name === "ssr") {
if (this.outputOptions.format === "app") {
// TODO
console.time(`[rolldown:${this.name}:hmr]`);
const result = await this.instance.experimental_hmr_rebuild([ctx.file]);
this.getRunner().evaluate(result[1].toString());
console.timeEnd(`[rolldown:${this.name}:hmr]`);
} else {
await this.build();
}
Expand All @@ -289,9 +293,22 @@ export class RolldownEnvironment extends DevEnvironment {
}
}

runner!: RolldownModuleRunner;

getRunner() {
if (!this.runner) {
const output = this.result.output[0];
const filepath = path.join(this.outDir, output.fileName);
this.runner = new RolldownModuleRunner();
const code = fs.readFileSync(filepath, "utf-8");
this.runner.evaluate(code);
}
return this.runner;
}

async import(input: string): Promise<unknown> {
if (this.outputOptions.format === "app") {
// TODO: eval or vm
return this.getRunner().import(input);
}
const output = this.result.output.find((o) => o.name === input);
assert(output, `invalid import input '${input}'`);
Expand All @@ -300,10 +317,54 @@ export class RolldownEnvironment extends DevEnvironment {
}
}

class RolldownModuleRunner {
// intercept globals
private context = {
rolldown_runtime: {} as any,
__rolldown_hot: {
send: () => {},
},
// TODO
// should be aware of importer for non static require/import.
// they needs to be transformed beforehand, so runtime can intercept.
require,
};

// TODO: support resolution?
async import(id: string): Promise<unknown> {
const mod = this.context.rolldown_runtime.moduleCache[id];
assert(mod, `Module not found '${id}'`);
return mod.exports;
}

evaluate(code: string) {
const context = {
self: this.context,
...this.context,
};
// TODO: sourcemap
code = code.replace(/^\/\/# sourceMapping.*$/m, "");
const wrapped = `'use strict';(${Object.keys(context).join(",")})=>{{
${code};
// TODO: need to re-expose runtime utilities for now
self.__toCommonJS = __toCommonJS;
self.__export = __export;
self.__toESM = __toESM;
}}`;
const fn = (0, eval)(wrapped);
try {
fn(...Object.values(context));
} catch (e) {
console.error(e);
}
}
}

// TODO: copy vite:build-html plugin
function viterollEntryPlugin(
config: ResolvedConfig,
viterollOptions: ViterollOptions,
environment: RolldownEnvironment,
): rolldown.Plugin {
const htmlEntryMap = new Map<string, MagicString>();

Expand Down Expand Up @@ -350,7 +411,10 @@ function viterollEntryPlugin(
if (code.includes("//#region rolldown:runtime")) {
const output = new MagicString(code);
// replace hard-coded WebSocket setup with custom one
output.replace(/const socket =.*?\n};/s, getRolldownClientCode(config));
output.replace(
/const socket =.*?\n};/s,
environment.name === "client" ? getRolldownClientCode(config) : "",
);
// trigger full rebuild on non-accepting entry invalidation
output
.replace("parents: [parent],", "parents: parent ? [parent] : [],")
Expand Down

0 comments on commit e67ffe3

Please sign in to comment.