Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(viteroll): inline react-refresh/runtime #72

Merged
merged 3 commits into from
Nov 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -117,3 +117,4 @@ jobs:
- run: pnpm test-e2e
- run: pnpm -C examples/react test-e2e
- run: pnpm -C examples/mpa test-e2e
- run: pnpm -C examples/ssr test-e2e
2 changes: 0 additions & 2 deletions viteroll/examples/ssr/src/entry-client.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
// TODO: how to inject automatically?
import "virtual:react-refresh/entry";
import React from "react";
import ReactDOMClient from "react-dom/client";
import { App } from "./app";
Expand Down
116 changes: 53 additions & 63 deletions viteroll/viteroll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,10 @@ export function viteroll(viterollOptions: ViterollOptions = {}): Plugin {
},
ssr: {
dev: {
createEnvironment:
RolldownEnvironment.createFactory(viterollOptions),
createEnvironment: RolldownEnvironment.createFactory({
...viterollOptions,
reactRefresh: false,
}),
},
},
},
Expand Down Expand Up @@ -315,9 +317,6 @@ function viterollEntryPlugin(
htmlEntryMap.set(id, htmlOutput);

let jsOutput = ``;
if (viterollOptions?.reactRefresh) {
jsOutput += `import "virtual:react-refresh/entry";\n`;
}

// extract <script src="...">
const matches = code.matchAll(
Expand Down Expand Up @@ -360,6 +359,9 @@ function viterollEntryPlugin(
}
for (var i = 0; i < module.parents.length; i++) {`,
);
if (viterollOptions.reactRefresh) {
output.prepend(getReactRefreshRuntimeCode());
}
return { code: output.toString(), map: output.generateMap() };
}
},
Expand Down Expand Up @@ -396,7 +398,6 @@ function viterollEntryPlugin(
};
}

// TODO: workaround rolldownExperimental.reactPlugin which injects js to html via `load` hook
function reactRefreshPlugin(): rolldown.Plugin {
return {
name: "react-hmr",
Expand All @@ -407,65 +408,54 @@ function reactRefreshPlugin(): rolldown.Plugin {
},
},
handler(code, id) {
const output = new MagicString(code);
output.prepend(`
import * as __$refresh from 'virtual:react-refresh';
const [$RefreshSig$, $RefreshReg$] = __$refresh.create(${JSON.stringify(id)});
`);
output.append(`
__$refresh.setupHot(module.hot);
`);
return { code: output.toString(), map: output.generateMap() };
return [
`const [$RefreshSig$, $RefreshReg$] = __react_refresh_transform_define(${JSON.stringify(id)})`,
code,
`__react_refresh_transform_setupHot(module.hot)`,
].join(";");
},
},
resolveId: {
filter: {
id: {
include: [/^virtual:react-refresh/],
},
},
handler: (source) => "\0" + source,
},
load: {
filter: {
id: {
include: [/^\0virtual:react-refresh/],
},
},
async handler(id) {
const resolved = require.resolve("react-refresh/runtime");
if (id === "\0virtual:react-refresh/entry") {
return `
import runtime from ${JSON.stringify(resolved)};
runtime.injectIntoGlobalHook(window);
`;
}
if (id === "\0virtual:react-refresh") {
return `
import runtime from ${JSON.stringify(resolved)};

export const create = (file) => [
runtime.createSignatureFunctionForTransform,
(type, id) => runtime.register(type, file + '_' + id),
];

function debounce(fn, delay) {
let handle
return () => {
clearTimeout(handle)
handle = setTimeout(fn, delay)
}
}
const debouncedRefresh = debounce(runtime.performReactRefresh, 16);
};
}

export function setupHot(hot) {
hot.accept((prev) => {
debouncedRefresh();
});
}
`;
// inject react refresh runtime in client runtime to ensure initialized early
function getReactRefreshRuntimeCode() {
let code = fs.readFileSync(
path.resolve(
require.resolve("react-refresh/runtime"),
"..",
"cjs/react-refresh-runtime.development.js",
),
"utf-8",
);
const output = new MagicString(code);
output.prepend("self.__react_refresh_runtime = {};\n");
output.replaceAll('process.env.NODE_ENV !== "production"', "true");
output.replaceAll(/\bexports\./g, "__react_refresh_runtime.");
output.append(`
(() => {
__react_refresh_runtime.injectIntoGlobalHook(self);

__react_refresh_transform_define = (file) => [
__react_refresh_runtime.createSignatureFunctionForTransform,
(type, id) => __react_refresh_runtime.register(type, file + '_' + id)
];

__react_refresh_transform_setupHot = (hot) => {
hot.accept((prev) => {
debouncedRefresh();
});
};

function debounce(fn, delay) {
let handle
return () => {
clearTimeout(handle)
handle = setTimeout(fn, delay)
}
},
},
};
}
const debouncedRefresh = debounce(__react_refresh_runtime.performReactRefresh, 16);
})()
`);
return output.toString();
}