Skip to content

Commit

Permalink
feat: react-refresh
Browse files Browse the repository at this point in the history
Co-authored-by: zhengyutay <zy>
  • Loading branch information
ZhengYuTay authored Dec 9, 2020
1 parent 9855b31 commit a624039
Show file tree
Hide file tree
Showing 13 changed files with 183 additions and 38 deletions.
2 changes: 2 additions & 0 deletions packages/runtime-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"node": ">= 12.0.0"
},
"dependencies": {
"@next/react-refresh-utils": "10.0.3",
"@shuvi/app": "^0.0.1-rc.16",
"@shuvi/router-react": "^0.0.1-rc.16",
"@shuvi/shared": "^0.0.1-rc.16",
Expand All @@ -36,6 +37,7 @@
"react": "16.13.0",
"react-app-polyfill": "1.0.6",
"react-dom": "16.13.0",
"react-refresh": "0.9.0",
"use-subscription": "1.4.1"
},
"devDependencies": {
Expand Down
56 changes: 55 additions & 1 deletion packages/runtime-react/src/lib/bundler/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import path from 'path';
import { IApi, APIHooks } from '@shuvi/types';
// @ts-ignore
import AliasPlugin from 'enhanced-resolve/lib/AliasPlugin';
import ReactRefreshWebpackPlugin from '@next/react-refresh-utils/ReactRefreshWebpackPlugin';
import { BUNDLER_TARGET_CLIENT } from '@shuvi/shared/lib/constants';
import { PACKAGE_DIR } from '../paths';
import { BUILD_CLIENT_RUNTIME_REACT_REFRESH } from '../constants';

export function config(api: IApi) {
const resolveLocal = (m: string) =>
Expand All @@ -11,7 +14,7 @@ export function config(api: IApi) {
path.join(api.paths.rootDir, 'node_modules', m);
api.tap<APIHooks.IHookBundlerConfig>('bundler:configTarget', {
name: 'runtime-react',
fn: config => {
fn: (config, { name }) => {
// const oriExternal = config.get("externals");
// const external: webpack.ExternalsFunctionElement = (
// context,
Expand Down Expand Up @@ -58,6 +61,57 @@ export function config(api: IApi) {
],
'resolve'
]);

if (name === BUNDLER_TARGET_CLIENT && api.mode === 'development') {
config.module
.rule('main')
.oneOf('js')
.use('react-refresh-loader')
.loader('@next/react-refresh-utils/loader')
.before('shuvi-babel-loader');

config.plugin('react-refresh-plugin').use(ReactRefreshWebpackPlugin);

config.module
.rule('main')
.oneOf('js')
.use('shuvi-babel-loader')
.tap(options => {
const plugins = options.plugins || [];
plugins.unshift([
require('react-refresh/babel'),
{ skipEnvCheck: true }
]);

return {
...options,
plugins
};
});

config.merge({
entry: {
[BUILD_CLIENT_RUNTIME_REACT_REFRESH]: [
require.resolve(`@next/react-refresh-utils/runtime`)
]
}
});

api.tap<APIHooks.IHookModifyHtml>('modifyHtml', {
name: 'insertReactRefreshEntryFile',
fn: documentProps => {
documentProps.scriptTags.unshift({
tagName: 'script',
attrs: {
src: api.getAssetPublicUrl(
BUILD_CLIENT_RUNTIME_REACT_REFRESH + '.js'
)
}
});
return documentProps;
}
});
}
return config;
}
});
Expand Down
1 change: 1 addition & 0 deletions packages/runtime-react/src/lib/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const BUILD_CLIENT_RUNTIME_REACT_REFRESH = `static/runtime/react-refresh`;
4 changes: 4 additions & 0 deletions packages/shared/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,8 @@ export const IDENTITY_SSR_RUNTIME_PUBLICPATH = `__${NAME}_ssr_public_path__`;
export const ROUTE_NOT_FOUND_NAME = `404`;

// bundle
export const BUNDLER_TARGET_CLIENT = `${NAME}/client`;

export const BUNDLER_TARGET_SERVER = `${NAME}/server`;

export const BUILD_SERVER_FILE_SERVER = `server`;
2 changes: 1 addition & 1 deletion packages/shuvi/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
"koa": "2.13.0",
"koa-connect": "2.1.0",
"send": "0.17.1",
"webpack": "5.7.0",
"webpack": "5.10.0",
"webpack-dev-middleware": "3.7.2"
},
"devDependencies": {
Expand Down
6 changes: 1 addition & 5 deletions packages/shuvi/src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import { NAME, PATH_PREFIX } from '@shuvi/shared/lib/constants';
import { PATH_PREFIX } from '@shuvi/shared/lib/constants';

export * from '@shuvi/shared/lib/constants';

export const BUNDLER_TARGET_CLIENT = `${NAME}/client`;

export const BUNDLER_TARGET_SERVER = `${NAME}/server`;

export const PUBLIC_PATH = `${PATH_PREFIX}/`;

export const BUILD_MANIFEST_PATH = 'build-manifest.json';
Expand Down
6 changes: 3 additions & 3 deletions packages/toolpack/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
"postcss-loader": "4.0.3",
"postcss-preset-env": "6.7.0",
"process": "0.11.10",
"react-error-overlay": "6.0.7",
"react-error-overlay": "6.0.8",
"sass": "1.26.12",
"sass-loader": "10.0.2",
"stream-browserify": "3.0.0",
Expand All @@ -73,9 +73,9 @@
"@types/terser-webpack-plugin": "4.2.0",
"@types/webpack-bundle-analyzer": "^3.8.0",
"memfs": "^3.1.2",
"webpack": "5.7.0"
"webpack": "5.10.0"
},
"peerDependencies": {
"webpack": "5.7.0"
"webpack": "5.10.0"
}
}
26 changes: 25 additions & 1 deletion packages/toolpack/src/utils/errorOverlayMiddleware.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,28 @@
import launchEditor from 'launch-editor';
import { Runtime } from '@shuvi/types';

function getSourcePath(source: string) {
// Webpack prefixes certain source paths with this path
if (source.startsWith('webpack:///')) {
return source.substring(11);
}

// Make sure library name is filtered out as well
if (source.startsWith('webpack://_N_E/')) {
return source.substring(15);
}

if (source.startsWith('webpack://')) {
return source.substring(10);
}

if (source.startsWith('/')) {
return source.substring(1);
}

return source;
}

export function createLaunchEditorMiddleware(
launchEditorEndpoint: string
): Runtime.IServerAppMiddleware {
Expand All @@ -9,7 +31,9 @@ export function createLaunchEditorMiddleware(
const { query } = (ctx.req as Runtime.IIncomingMessage).parsedUrl;
const lineNumber = parseInt(query.lineNumber as string, 10) || 1;
const colNumber = parseInt(query.colNumber as string, 10) || 1;
launchEditor(`${query.fileName}:${lineNumber}:${colNumber}`);
launchEditor(
getSourcePath(`${query.fileName}:${lineNumber}:${colNumber}`)
);
ctx.body = '';
return;
} else {
Expand Down
43 changes: 30 additions & 13 deletions packages/toolpack/src/utils/hotDevClient/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,9 @@ function clearOutdatedErrors() {
}
}

function afterApplyUpdate(hasUpdates) {
function afterApplyUpdate() {
tryDismissErrorOverlay();
console.log('[Fast Refresh] Done.');
}

// Successful compilation.
Expand All @@ -137,10 +138,10 @@ function handleSuccess() {

// Attempt to apply hot updates or reload.
if (isHotUpdate) {
tryApplyUpdates(function onHotUpdateSuccess(hasUpdates) {
tryApplyUpdates(function onHotUpdateSuccess() {
// Only dismiss it when we're sure it's a hot update.
// Otherwise it would flicker right before the reload.
afterApplyUpdate(hasUpdates);
afterApplyUpdate();
});
}
}
Expand All @@ -149,6 +150,10 @@ function handleSuccess() {
function handleWarnings(warnings) {
clearOutdatedErrors();

var isHotUpdate = !isFirstCompilation;
isFirstCompilation = false;
hasCompileErrors = false;

// Print warnings to the console.
const formatted = formatWebpackMessages({
warnings: warnings,
Expand All @@ -167,6 +172,15 @@ function handleWarnings(warnings) {
console.warn(stripAnsi(formatted.warnings[i]));
}
}

// Attempt to apply hot updates or reload.
if (isHotUpdate) {
tryApplyUpdates(function onSuccessfulHotUpdate() {
// Only dismiss it when we're sure it's a hot update.
// Otherwise it would flicker right before the reload.
tryDismissErrorOverlay();
});
}
}

// Compilation with errors (e.g. syntax error or missing modules).
Expand Down Expand Up @@ -211,7 +225,9 @@ function processMessage(e) {
switch (obj.action) {
case 'building': {
console.log(
'[HMR] bundle ' + (obj.name ? "'" + obj.name + "' " : '') + 'rebuilding'
'[Fast Refresh] bundle ' +
(obj.name ? "'" + obj.name + "' " : '') +
'rebuilding'
);
break;
}
Expand Down Expand Up @@ -277,26 +293,27 @@ async function tryApplyUpdates(onHotUpdateSuccess) {

if (!isUpdateAvailable() || !canApplyUpdates()) {
ErrorOverlay.dismissBuildError();
// HMR failed, need to refresh
if (module.hot.status() === 'fail') {
window.location.reload();
}
return;
}

function handleApplyUpdates(err, updatedModules) {
const needForcedReload = err || !updatedModules || hadRuntimeError;
if (needForcedReload) {
if (err) {
console.warn('Error while applying updates, reloading page', err);
}
ErrorOverlay.reportRuntimeError(err);
if (hadRuntimeError) {
console.warn('Had runtime error previously, reloading page');
hadRuntimeError = false;
window.location.reload();
}
window.location.reload();
return;
hadRuntimeError = true;
}

const hasUpdates = Boolean(updatedModules.length);
if (typeof onHotUpdateSuccess === 'function') {
// Maybe we want to do something.
onHotUpdateSuccess(hasUpdates);
onHotUpdateSuccess();
}

if (isUpdateAvailable()) {
Expand All @@ -305,7 +322,7 @@ async function tryApplyUpdates(onHotUpdateSuccess) {
}
}

// https://webpack.github.io/docs/hot-module-replacement.html#check
// https://webpack.js.org/api/hot-module-replacement/#check
try {
const updatedModules = await module.hot.check(/* autoApply */ true);
handleApplyUpdates(null, updatedModules);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ module.exports = babelLoader.custom((babel: any) => {
);

delete loader.isNode;

return { loader, custom };
},
config(
Expand Down
2 changes: 1 addition & 1 deletion packages/types/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"@types/webpack-dev-middleware": "3.7.2",
"http-proxy-middleware": "1.0.6",
"query-string": "6.13.1",
"webpack": "5.7.0",
"webpack": "5.10.0",
"webpack-chain": "6.5.1"
}
}
6 changes: 4 additions & 2 deletions packages/types/src/hooks.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import { IUserRouteConfig, IDocumentProps } from './runtime';
import { IWebpackHelpers } from './bundler';

type ExtractSyncHookGeneric<Type> = Type extends SyncHook<infer X> ? X : never;
export type MultiStats = ExtractSyncHookGeneric<MultiCompiler['hooks']['done']>[0];
export type MultiStats = ExtractSyncHookGeneric<
MultiCompiler['hooks']['done']
>[0];

export type IHookGetConfig = defineHook<
'getConfig',
Expand Down Expand Up @@ -80,7 +82,7 @@ export type IEventBundlerDone = defineHook<
args: [
{
first: boolean;
stats: MultiStats;
stats: any;
}
];
}
Expand Down
Loading

0 comments on commit a624039

Please sign in to comment.