Skip to content

Commit

Permalink
improve hmr for default exports (#187)
Browse files Browse the repository at this point in the history
  • Loading branch information
lifeart authored Jan 14, 2025
1 parent 3bbd1a7 commit e33e98a
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 28 deletions.
60 changes: 45 additions & 15 deletions plugins/babel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export function processTemplate(
return {
name: 'ast-transform', // not required
visitor: {
VariableDeclarator(path: any, context: Context) {
VariableDeclarator(path: Babel.NodePath<Babel.types.VariableDeclarator>, context: Context) {
if (mode !== 'development') {
return;
}
Expand All @@ -47,7 +47,7 @@ export function processTemplate(
}
}
},
ExportNamedDeclaration(path: any, context: Context) {
ExportNamedDeclaration(path: Babel.NodePath<Babel.types.ExportNamedDeclaration>, context: Context) {
if (mode !== 'development') {
return;
}
Expand All @@ -66,15 +66,44 @@ export function processTemplate(
}
} else if (path.node.declaration.type === 'ClassDeclaration') {
const declaration = path.node.declaration;
if (declaration.id.type === 'Identifier') {
if (declaration.id?.type === 'Identifier') {
const existingTokens = context.tokensForHotReload as string[];
existingTokens.push(declaration.id.name);
}
} else if (path.node.declaration.type === 'FunctionDeclaration') {
const declaration = path.node.declaration;
if (declaration.id?.type === 'Identifier') {
const existingTokens = context.tokensForHotReload as string[];
existingTokens.push(declaration.id.name);
}
}
}
},
ExportDefaultDeclaration(path: Babel.NodePath<Babel.types.ExportDefaultDeclaration>, context: Context) {
if (mode !== 'development') {
return;
}
if (!context.tokensForHotReload) {
context.tokensForHotReload = [];
}
if (path.node.declaration) {
if (path.node.declaration.type === 'ClassDeclaration' && path.node.declaration.id) {
const declaration = path.node.declaration;
if (declaration.id?.type === 'Identifier') {
const existingTokens = context.tokensForHotReload as string[];
existingTokens.push(`${declaration.id.name}:default`);
}
} else if (path.node.declaration.type === 'FunctionDeclaration' && path.node.declaration.id) {
const declaration = path.node.declaration;
if (declaration.id?.type === 'Identifier') {
const existingTokens = context.tokensForHotReload as string[];
existingTokens.push(`${declaration.id.name}:default`);
}
}
}
},
ClassBody: {
enter(_: any, context: Context) {
enter(_: Babel.NodePath<Babel.types.ClassBody>, context: Context) {
// here we assume that class is extends from our Component
// @todo - check if it's really extends from Component
context.isInsideClassBody = true;
Expand All @@ -83,18 +112,19 @@ export function processTemplate(
context.isInsideClassBody = false;
}
},
exit(_: any, context: Context) {
exit(_: Babel.NodePath<Babel.types.ClassBody>, context: Context) {
context.isInsideClassBody = false;
},
},
ClassMethod(path: any) {
if (path.node.key.name === '$static') {
ClassMethod(path: Babel.NodePath<Babel.types.ClassMethod>) {
if (path.node.key.type === 'Identifier' && path.node.key.name === '$static') {
path.replaceWith(
t.classProperty(
t.identifier(SYMBOLS.$template),
// hbs literal
t.taggedTemplateExpression(
t.identifier('hbs'),
// @ts-expect-error expression type
path.node.body.body[0].expression.arguments[0],
),
null,
Expand All @@ -104,15 +134,15 @@ export function processTemplate(
);
}
},
CallExpression(path: any) {
CallExpression(path: Babel.NodePath<Babel.types.CallExpression>) {
if (path.node.callee && path.node.callee.type === 'Identifier') {
if (path.node.callee.name === 'scope') {
path.remove();
} else if (path.node.callee.name === 'template') {
path.replaceWith(
t.taggedTemplateExpression(
t.identifier('hbs'),
path.node.arguments[0],
path.node.arguments[0] as Babel.types.TemplateLiteral,
),
);
} else if (path.node.callee.name === 'formula') {
Expand All @@ -132,7 +162,7 @@ export function processTemplate(
}
}
},
ImportDeclaration(path: any) {
ImportDeclaration(path: Babel.NodePath<Babel.types.ImportDeclaration>) {
if (path.node.source.value === '@ember/template-compiler') {
path.node.source.value = MAIN_IMPORT;
path.node.specifiers.forEach((specifier: any) => {
Expand All @@ -141,7 +171,7 @@ export function processTemplate(
});
}
},
Program(path: any) {
Program(path: Babel.NodePath<Babel.types.Program>) {
const PUBLIC_API = Object.values(SYMBOLS);
const IMPORTS = PUBLIC_API.map((name) => {
return t.importSpecifier(t.identifier(name), t.identifier(name));
Expand All @@ -151,15 +181,15 @@ export function processTemplate(
);
},
ReturnStatement: {
enter(_: any, context: Context) {
enter(_: Babel.NodePath<Babel.types.ReturnStatement>, context: Context) {
context.isInsideReturnStatement = true;
},
exit(_: any, context: Context) {
exit(_: Babel.NodePath<Babel.types.ReturnStatement>, context: Context) {
context.isInsideReturnStatement = false;
},
},
TaggedTemplateExpression(path: any, context: Context) {
if (path.node.tag.name === 'hbs') {
TaggedTemplateExpression(path: Babel.NodePath<Babel.types.TaggedTemplateExpression>, context: Context) {
if (path.node.tag.type === 'Identifier' && path.node.tag.name === 'hbs') {
const template = path.node.quasi.quasis[0].value.raw as string;
const isInsideClassBody = context.isInsideClassBody === true;
const hasThisInTemplate = template.includes('this');
Expand Down
16 changes: 11 additions & 5 deletions plugins/hmr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,32 @@ export function shouldHotReloadFile(fileName: string, code: string) {
export const HMR = `
if (import.meta.hot) {
const existingTokensToReload: string[] = [];
const evalMap = {};
const internalTokensToReload = existingTokensToReload.map((t) => {
const [key, value] = t.split(':');
evalMap[key] = value || key;
return key;
});
import.meta.hot.accept((newModule) => {
if (newModule) {
const moduleTokens = Object.keys(newModule);
const newTokens = moduleTokens.filter(
(token) => !existingTokensToReload.includes(token),
(token) => !(internalTokensToReload.includes(token) || Array.from(Object.values(evalMap)).includes(token)),
);
if (
newTokens.length ||
moduleTokens.length !== existingTokensToReload.length
moduleTokens.length !== internalTokensToReload.length
) {
import.meta.hot?.invalidate();
} else {
moduleTokens.forEach((token) => {
const oldModule = existingTokensToReload.find((t) => t === token);
const oldModule = internalTokensToReload.find((t) => evalMap[t] === token);
if (oldModule) {
window.hotReload(eval(oldModule), newModule[token]);
}
});
existingTokensToReload.length = 0;
existingTokensToReload.push(...moduleTokens);
internalTokensToReload.length = 0;
internalTokensToReload.push(...moduleTokens);
}
}
});
Expand Down
41 changes: 33 additions & 8 deletions src/utils/control-flow/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
cId,
isEmpty,
CHILD,
TREE,
} from '@/utils/shared';
import { isRehydrationScheduled } from '@/utils/ssr/rehydration';
import { initDOM } from '@/utils/context';
Expand All @@ -33,15 +34,30 @@ export function getFirstNode(
rawItem:
| Node
| ComponentReturnType
| Component
| GenericReturnType
| Array<Node | ComponentReturnType | GenericReturnType>,
): Node {
if (isArray(rawItem)) {
return getFirstNode(rawItem[0]);
} else if ('nodeType' in rawItem) {
return rawItem;
} else if ('ctx' in rawItem) {
return getFirstNode(rawItem.ctx!);
} else {
return rawItem.ctx![RENDERED_NODES_PROPERTY][0];
return (
rawItem![RENDERED_NODES_PROPERTY][0] ||
Array.from(CHILD.get(rawItem![COMPONENT_ID_PROPERTY]) ?? []).reduce(
(acc: null | Node, item: number) => {
if (!acc) {
return getFirstNode(TREE.get(item)!);
} else {
return acc;
}
},
null,
)
);
}
}

Expand All @@ -51,7 +67,10 @@ export function getFirstNode(
Based on Glimmer-VM list update logic.
*/
type GenericReturnType = Array<ComponentReturnType | Node> | ComponentReturnType | Node;
type GenericReturnType =
| Array<ComponentReturnType | Node>
| ComponentReturnType
| Node;

type ListComponentArgs<T> = {
tag: Cell<T[]> | MergedCell;
Expand Down Expand Up @@ -102,8 +121,8 @@ export class BasicListComponent<T extends { id: number }> {
}
declare api: typeof HTML_API;
declare args: {
[$context]: Component<any>
}
[$context]: Component<any>;
};
constructor(
{ tag, ctx, key, ItemComponent }: ListComponentArgs<T>,
outlet: RenderTarget,
Expand All @@ -113,7 +132,7 @@ export class BasicListComponent<T extends { id: number }> {
this.ItemComponent = ItemComponent;
this.args = {
[$context]: ctx,
}
};
// @ts-expect-error typings error
addToTree(ctx, this, 'from list constructor');
const mainNode = outlet;
Expand Down Expand Up @@ -331,7 +350,11 @@ export class BasicListComponent<T extends { id: number }> {
? getFirstNode(keyMap.get(keyForItem(nextItem, index + 1))!)
: bottomMarker;
// node relocation, assume we have only once root node :)
api.insert(insertBeforeNode.parentNode!, getFirstNode(row), insertBeforeNode)
api.insert(
insertBeforeNode.parentNode!,
getFirstNode(row),
insertBeforeNode,
);
});
if (targetNode !== bottomMarker) {
const parent = targetNode.parentNode!;
Expand Down Expand Up @@ -359,7 +382,8 @@ export class SyncListComponent<
topMarker: Comment,
) {
super(params, outlet, topMarker);
registerDestructor(params.ctx,
registerDestructor(
params.ctx,
() => this.syncList([]),
opcodeFor(this.tag, (value) => {
this.syncList(value as T[]);
Expand Down Expand Up @@ -443,7 +467,8 @@ export class AsyncListComponent<
topMarker: Comment,
) {
super(params, outlet, topMarker);
registerDestructor(params.ctx,
registerDestructor(
params.ctx,
() => {
if (this.destroyPromise) {
return this.destroyPromise;
Expand Down

0 comments on commit e33e98a

Please sign in to comment.