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

feat(hmr): add in hmr capabilities #1400

Merged
merged 33 commits into from
May 5, 2022
Merged
Changes from 1 commit
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
1273a7a
feat(hmr): add in hmr capabilities
brandonseydel Apr 25, 2022
b725434
Merge branch 'master' into feat/hmr
bigopon Apr 25, 2022
6e3efe4
chore(fix): cleanup some code and make it work for decorated classes …
brandonseydel Apr 25, 2022
1f4308d
Merge branch 'feat/hmr' of https://github.com/aurelia/aurelia into fe…
brandonseydel Apr 25, 2022
f375857
chore(clean): remove console.log
brandonseydel Apr 25, 2022
2b802d6
chore(simp): break out some more hmr code
brandonseydel Apr 25, 2022
8b59fad
fix(seperate): break out the code for conventions
brandonseydel Apr 25, 2022
700dfba
chore(refactor): change interface naming
brandonseydel Apr 25, 2022
0759290
fix(typings): fix typings issue
brandonseydel Apr 25, 2022
52337b5
fix(test): fix failing tests and lint
brandonseydel Apr 25, 2022
f70a882
fix(lint): remove unused import
brandonseydel Apr 25, 2022
7566d65
chore(lint): fix linting issues
brandonseydel Apr 25, 2022
03141d7
fix(hmr): make it work for html only templates
brandonseydel Apr 26, 2022
3d907dd
chore(tests): fix the tests to ignore hmr
brandonseydel Apr 26, 2022
a2a8505
fix(html): tweak the code for html templates
brandonseydel Apr 26, 2022
d7b3288
Merge branch 'master' into feat/hmr
bigopon Apr 26, 2022
fd7307c
fix(tests): fix some tests
brandonseydel Apr 26, 2022
6c1613a
fix(build): fix some build errors
brandonseydel Apr 27, 2022
f8fd73a
fix(tests): fix some tests
brandonseydel Apr 27, 2022
9c28505
fix(lint): fix some linting issues
brandonseydel Apr 27, 2022
e5b70a1
fix(test): fix all the tests
brandonseydel Apr 27, 2022
f7a656a
fix(lint): fix issue with curly
brandonseydel Apr 27, 2022
950c425
fix(lint): fix lint error again
brandonseydel Apr 27, 2022
192f60c
fix(tests): fix some tests
brandonseydel Apr 27, 2022
89faa49
chore(fix): remove interfaces we don't need any longer from import
brandonseydel Apr 27, 2022
122a1c8
Merge branch 'master' of https://github.com/aurelia/aurelia into feat…
bigopon Apr 28, 2022
f419318
fix(hmr): also import IHydrationContext
bigopon Apr 28, 2022
b45fc05
feat(vite): prepare for vite hmr
brandonseydel Apr 28, 2022
dc30899
Merge branch 'feat/hmr' of https://github.com/aurelia/aurelia into fe…
brandonseydel Apr 28, 2022
11ca2ca
fix(vite): for some reason the accept has to be off the import directly
brandonseydel Apr 28, 2022
7886938
fix(tests): add in explicit hmrmodule for testing
brandonseydel Apr 29, 2022
3028b40
Merge branch 'master' into feat/hmr
bigopon May 5, 2022
0561f22
fix(hmr): reexport LifecycleFlags from runtime
bigopon May 5, 2022
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
114 changes: 106 additions & 8 deletions packages-cjs/plugin-conventions/src/preprocess-resource.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import modifyCode, { ModifyCodeResult } from 'modify-code';
import { stringify } from 'querystring';
import * as ts from 'typescript';
import { nameConvention } from './name-convention.js';
import { IFileUnit, IPreprocessOptions, ResourceType } from './options.js';
Expand All @@ -16,6 +17,7 @@ interface IPos {
}

interface IFoundResource {
className?: string;
localDep?: string;
needDecorator?: [number, string];
implicitStatement?: IPos;
Expand All @@ -29,19 +31,25 @@ interface IFoundDecorator {
}

interface IModifyResourceOptions {
exportedClassName?: string;
metadataImport: ICapturedImport;
runtimeImport: ICapturedImport;
implicitElement?: IPos;
localDeps: string[];
conventionalDecorators: [number, string][];
customElementName?: IPos;
}

const hmrRuntimeModules = ['ICustomElementViewModel', 'CustomElement', 'LifecycleFlags', 'IHydrationContext', 'Controller'];
const hmrMetadataModules = ['Metadata'];

export function preprocessResource(unit: IFileUnit, options: IPreprocessOptions): ModifyCodeResult {
const expectedResourceName = resourceName(unit.path);
const sf = ts.createSourceFile(unit.path, unit.contents, ts.ScriptTarget.Latest);

let exportedClassName: string | undefined;
let auImport: ICapturedImport = { names: [], start: 0, end: 0 };
let runtimeImport: ICapturedImport = { names: [], start: 0, end: 0 };
let metadataImport: ICapturedImport = { names: [], start: 0, end: 0 };

let implicitElement: IPos | undefined;
let customElementName: IPos | undefined; // for @customName('custom-name')
Expand All @@ -60,6 +68,14 @@ export function preprocessResource(unit: IFileUnit, options: IPreprocessOptions)
return;
}

// Find existing import {Metadata} from '@aurelia/metadata';
const metadata = captureImport(s, '@aurelia/metadata', unit.contents);
if (metadata) {
// Assumes only one import statement for @aurelia/runtime-html
metadataImport = metadata;
return;
}

// Find existing import {customElement} from '@aurelia/runtime-html';
const runtime = captureImport(s, '@aurelia/runtime-html', unit.contents);
if (runtime) {
Expand All @@ -75,6 +91,7 @@ export function preprocessResource(unit: IFileUnit, options: IPreprocessOptions)
const resource = findResource(s, expectedResourceName, unit.filePair, unit.isViewPair, unit.contents);
if (!resource) return;
const {
className,
localDep,
needDecorator,
implicitStatement,
Expand All @@ -88,11 +105,24 @@ export function preprocessResource(unit: IFileUnit, options: IPreprocessOptions)
if (runtimeImportName && !auImport.names.includes(runtimeImportName)) {
ensureTypeIsExported(runtimeImport.names, runtimeImportName);
}
if (className) exportedClassName = className;
hmrRuntimeModules.forEach(m => {
if (!auImport.names.includes(m)) {
ensureTypeIsExported(runtimeImport.names, m);
}
});
hmrMetadataModules.forEach(m => {
if (!auImport.names.includes(m)) {
ensureTypeIsExported(metadataImport.names, m);
}
});
if (customName) customElementName = customName;
});

return modifyResource(unit, {
runtimeImport,
metadataImport,
exportedClassName,
implicitElement,
localDeps,
conventionalDecorators,
Expand All @@ -102,6 +132,8 @@ export function preprocessResource(unit: IFileUnit, options: IPreprocessOptions)

function modifyResource(unit: IFileUnit, options: IModifyResourceOptions) {
const {
metadataImport,
exportedClassName,
runtimeImport,
implicitElement,
localDeps,
Expand Down Expand Up @@ -135,13 +167,21 @@ function modifyResource(unit: IFileUnit, options: IModifyResourceOptions) {
if (customElementName) {
// Overwrite element name
const name = unit.contents.slice(customElementName.pos, customElementName.end);
m.replace(customElementName.pos, customElementName.end,`{ ...${viewDef}, name: ${name} }`);
m.replace(customElementName.pos, customElementName.end, `{ ...${viewDef}, name: ${name} }`);
} else {
conventionalDecorators.push([implicitElement.pos, `@${dec}(${viewDef})\n`]);
}
}
}

if (metadataImport.names.length) {
let metadataImportStatement = `import { Metadata } from '@aurelia/metadata';`;
if (metadataImport.end === metadataImport.start)
metadataImportStatement += '\n';
console.log(metadataImportStatement);
m.replace(metadataImport.start, metadataImport.end, metadataImportStatement);
}

if (conventionalDecorators.length) {
if (runtimeImport.names.length) {
let runtimeImportStatement = `import { ${runtimeImport.names.join(', ')} } from '@aurelia/runtime-html';`;
Expand All @@ -152,16 +192,71 @@ function modifyResource(unit: IFileUnit, options: IModifyResourceOptions) {
conventionalDecorators.forEach(([pos, str]) => m.insert(pos, str));
}

if (exportedClassName && process.env.NODE_ENV !== 'production') {
brandonseydel marked this conversation as resolved.
Show resolved Hide resolved
const hmr = `
brandonseydel marked this conversation as resolved.
Show resolved Hide resolved
if ((module as any).hot) {
let aurelia = (module as any).hot.data?.aurelia;
document.addEventListener('au-started', (event) => {aurelia= (event as any).detail; });
const hot = (module as any).hot;
const controllers: Controller<ICustomElementViewModel>[] = [];
const ogCreated = (${exportedClassName}.prototype as any).created;

(${exportedClassName}.prototype as any).created = (controller) => {
ogCreated && ogCreated(controller);
controllers.push(controller as Controller<ICustomElementViewModel>);
}

hot.accept();
hot.dispose(function (data) {
data.controllers = controllers;
data.aurelia = aurelia;
});

if (hot.data?.aurelia) {

const newDefinition = CustomElement.getDefinition(${exportedClassName});
Metadata.define(newDefinition.name, newDefinition, ${exportedClassName});
Metadata.define(newDefinition.name, newDefinition, newDefinition);
(hot.data.aurelia.container as any).res[CustomElement.keyFrom(newDefinition.name)] = newDefinition;


(hot.data.controllers as typeof controllers).forEach(controller => {
const values = { ...controller.viewModel };
const hydrationContext = controller.container.get(IHydrationContext)
const hydrationInst = hydrationContext.instruction;

Object.keys(values).forEach(key => {
if (!controller.bindings?.some(y => (y as any).sourceExpression?.name === key && (y as any).targetProperty)) {
delete values[key];
}
});
const h = (controller as any).host;
delete (controller as any)._compiledDef;
(controller.viewModel as any) = new ${exportedClassName}();
(controller.definition as any) = newDefinition;
Object.assign(controller.viewModel, values);
(controller.hooks as any) = new (controller.hooks as any).constructor(controller.viewModel);
(controller as any)._hydrateCustomElement(hydrationInst, hydrationContext);
h.parentNode.replaceChild((controller as any).host, h);
(controller as any).hostController = null;
(controller as any).deactivate(controller, controller.parent ?? null, LifecycleFlags.none);
(controller as any).activate(controller, controller.parent ?? null, LifecycleFlags.none);
});
}
}`;
m.append(hmr);
}

return m.transform();
}

function captureImport(s: ts.Statement, lib: string, code: string): ICapturedImport | void {
if (ts.isImportDeclaration(s) &&
ts.isStringLiteral(s.moduleSpecifier) &&
s.moduleSpecifier.text === lib &&
s.importClause &&
s.importClause.namedBindings &&
ts.isNamedImports(s.importClause.namedBindings)) {
ts.isStringLiteral(s.moduleSpecifier) &&
s.moduleSpecifier.text === lib &&
s.importClause &&
s.importClause.namedBindings &&
ts.isNamedImports(s.importClause.namedBindings)) {
return {
names: s.importClause.namedBindings.elements.map(e => e.name.text),
start: ensureTokenStart(s.pos, code),
Expand Down Expand Up @@ -222,7 +317,7 @@ function findResource(node: ts.Node, expectedResourceName: string, filePair: str
const pos = ensureTokenStart(node.pos, code);

const className = node.name.text;
const {name, type} = nameConvention(className);
const { name, type } = nameConvention(className);
const isImplicitResource = isKindOfSame(name, expectedResourceName);
const foundType = findDecoratedResourceType(node);

Expand All @@ -245,6 +340,7 @@ function findResource(node: ts.Node, expectedResourceName: string, filePair: str
// @customElement('custom-name')
const customName = foundType.expression.arguments[0] as ts.StringLiteral;
return {
className,
implicitStatement: { pos: pos, end: node.end },
customName: { pos: ensureTokenStart(customName.pos, code), end: customName.end }
};
Expand All @@ -254,12 +350,14 @@ function findResource(node: ts.Node, expectedResourceName: string, filePair: str
// Custom element can only be implicit resource
if (isImplicitResource && filePair) {
return {
className,
implicitStatement: { pos: pos, end: node.end },
runtimeImportName: isViewPair ? 'view' : 'customElement'
};
}
} else {
const result: IFoundResource = {
className,
needDecorator: [pos, `@${type}('${name}')\n`],
localDep: className,
};
Expand Down