Skip to content

Commit

Permalink
Merge pull request #323 from meteor/release-2.4
Browse files Browse the repository at this point in the history
Release 2.4
  • Loading branch information
filipenevola authored Apr 12, 2021
2 parents da072c5 + 80fbd1d commit 8e1eb8c
Show file tree
Hide file tree
Showing 77 changed files with 2,082 additions and 1,312 deletions.
14 changes: 14 additions & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
## vNEXT

## v.2.4.0, 2021-March-26?

* [#313](https://github.com/meteor/blaze/pull/313) Implemented HMR for Blaze

* [#319](https://github.com/meteor/blaze/pull/319) Fixed a few situations where the template compiler wouldn't optimise it's output javascript. Should make rendering faster (if the initial optimisation reasoning holds true)

* [#321](https://github.com/meteor/blaze/pull/321) Just source code modernisation, making it easier to read. Shouldn't change any API's; except may need explicit import if other packages are using directly.

* [#324](https://github.com/meteor/blaze/pull/324) Add a whitespace="strip" option to templates, which removes any whitespace that crosses newlines.

* [#276](https://github.com/meteor/blaze/pull/276) [HTML.isArray](https://github.com/brucejo75/blaze/blob/release-2.4/packages/htmljs/README.md#htmlisarrayx) works across iFrames. This supports running blaze in sandboxed iFrames.

* [#326](https://github.com/meteor/blaze/pull/326) Remove old ui package code from this repository.

## v2.3.4, 2019-Dec-13

* jquery 3 support
Expand Down
140 changes: 140 additions & 0 deletions packages/blaze-hot/hot.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import { Blaze } from 'meteor/blaze';
import { Template as Templates} from 'meteor/templating-runtime';
import { UpdateAll } from './update-templates.js';

let importedTemplating = new WeakMap();
let currentModule = {id: null};
const SourceModule = Symbol();

function patchTemplate(Template) {
const oldRegisterHelper = Template.registerHelper;
Template.registerHelper = function (name, func) {
func[SourceModule] = currentModule.id;
oldRegisterHelper(name, func);
}

const oldOnCreated = Template.prototype.onCreated;
Template.prototype.onCreated = function (cb) {
if (cb) {
cb[SourceModule] = currentModule.id;
}

return oldOnCreated.call(this, cb);
}

const oldOnRendered = Template.prototype.onRendered;
Template.prototype.onRendered = function (cb) {
if (cb) {
cb[SourceModule] = currentModule.id;
}

return oldOnRendered.call(this, cb);
}

const oldOnDestroyed = Template.prototype.onDestroyed;
Template.prototype.onDestroyed = function (cb) {
if (cb) {
cb[SourceModule] = currentModule.id;
}

return oldOnDestroyed.call(this, cb);
}

const oldHelpers = Template.prototype.helpers;
Template.prototype.helpers = function (dict) {
if (typeof dict === 'object') {
for (var k in dict) {
if (dict[k]) {
dict[k][SourceModule] = currentModule.id;
}
}
}

return oldHelpers.call(this, dict);
}

const oldEvents = Template.prototype.events;
Template.prototype.events = function (eventMap) {
const result = oldEvents.call(this, eventMap);
this.__eventMaps[this.__eventMaps.length - 1][SourceModule] = currentModule.id;
return result;
}
}

function cleanTemplate(template, moduleId) {
let usedModule = false
if (!template || !Blaze.isTemplate(template)) {
return usedModule;
}

function cleanArray(array) {
for (let i = array.length - 1; i >= 0; i--) {
let item = array[i];
if (item && item[SourceModule] === moduleId) {
usedModule = true
array.splice(i, 1);
}
}
}

cleanArray(template._callbacks.created);
cleanArray(template._callbacks.rendered);
cleanArray(template._callbacks.destroyed);
cleanArray(template.__eventMaps);

Object.keys(template.__helpers).forEach(key => {
if (template.__helpers[key] && template.__helpers[key][SourceModule] === moduleId) {
usedModule = true
delete template.__helpers[key];
}
});

return usedModule
}

function shouldAccept(module) {
if (!importedTemplating.get(module)) {
return false;
}
if (!module.exports) {
return true;
}

return Object.keys(module.exports).filter(key => key !== '__esModule').length === 0;
}

if (module.hot) {
patchTemplate(Blaze.Template);
module.hot.onRequire({
before(module) {
if (module.id === '/node_modules/meteor/blaze.js' || module.id === '/node_modules/meteor/templating.js') {
importedTemplating.set(currentModule, true);
}

let previousModule = currentModule;
currentModule = module;
return previousModule;
},
after(module, previousModule) {
if (shouldAccept(module)) {
module.hot.accept();
module.hot.dispose(() => {
Object.keys(Templates).forEach(templateName => {
let template = Templates[templateName]
let usedByModule = cleanTemplate(template, module.id);
if (usedByModule) {
Template._applyHmrChanges(templateName);
}
});

Object.values(Blaze._globalHelpers).forEach(helper => {
if (helper && helper[SourceModule] === module.id) {
Template._applyHmrChanges(UpdateAll);
}
});
});
}
currentModule = previousModule
}
});
}
20 changes: 20 additions & 0 deletions packages/blaze-hot/package.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
Package.describe({
name: 'blaze-hot',
summary: "Update files using Blaze's API with HMR",
version: '1.0.0-beta.3',
git: 'https://github.com/meteor/blaze.git',
documentation: null,
debugOnly: true
});

Package.onUse(function (api) {
api.use('modules@0.15.0');
api.use('ecmascript@0.14.4');
api.use('blaze@2.4.0-beta.3');
api.use('underscore@1.0.9');
api.use('templating-runtime@1.4.0-beta.3');
api.use('hot-module-replacement@0.2.0', { weak: true });

api.addFiles('hot.js', 'client');
api.addFiles('update-templates.js', 'client');
});
164 changes: 164 additions & 0 deletions packages/blaze-hot/update-templates.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
export const UpdateAll = Symbol('update all templates')

let renderedTemplates = {};
let overrideTemplateContentBlock = null;
let overrideTemplateElseBlock = null;

const oldConstructView = Template.prototype.constructView;

Template.prototype.constructView = function () {
let view = oldConstructView.apply(this, arguments);
let templateName = this.viewName;

view.onViewCreated(function () {
renderedTemplates[templateName] = renderedTemplates[templateName] || [];
renderedTemplates[templateName].push(view);
});

view.onViewDestroyed(function () {
const index = renderedTemplates[templateName].indexOf(view);
if (index > -1) {
renderedTemplates[templateName].splice(index, 1);
}
});

if (overrideTemplateContentBlock) {
view.templateContentBlock = overrideTemplateContentBlock;
overrideTemplateContentBlock = null;
}

if (overrideTemplateElseBlock) {
view.templateElseBlock = overrideTemplateElseBlock;
overrideTemplateElseBlock = null;
}

return view;
}

let updateRootViews = Template._applyHmrChanges;

let timeout = null;
let lastUpdateFailed = false;
let modifiedTemplates = new Set();
let templateViewPrefix = 'Template.';

// Overrides the default _applyHmrChanges with one that updates the specific
// views for modified templates instead of updating everything.
Template._applyHmrChanges = function (templateName = UpdateAll) {
if (templateName === UpdateAll || lastUpdateFailed) {
lastUpdateFailed = false;
clearTimeout(timeout);
updateRootViews();
return;
} else {
modifiedTemplates.add(templateName);
}

if (timeout) {
return;
}

timeout = setTimeout(() => {
for (var i = 0; i < Template.__pendingReplacement.length; i++) {
delete Template[Template.__pendingReplacement[i]]
}

Template.__pendingReplacement = [];

timeout = null;
modifiedTemplates.forEach(templateName => {
modifiedTemplates.delete(templateName);
let viewName = templateName;

if (!(viewName in renderedTemplates)) {
viewName = templateViewPrefix + viewName;
} else {
console.error('[Blaze HMR] Error: view name does not start with Template');
return;
}

if (!(viewName in renderedTemplates)) {
return;
}

let views = renderedTemplates[viewName];
renderedTemplates[viewName] = [];
while (views.length > 0) {
let view = views.pop();

// find first parent template that isn't a content block
while (!view.template || view.templateContentBlock || view.templateElseBlock) {
if (!view.parentView) {
console.log('[Blaze HMR] Unable to update template', viewName);
return;
}

view = view.parentView;
}

if (!view.isRendered) {
continue;
}

// TODO: this can be removed if we don't update a view, and then update
// one of its children (we only need to update the parent).
Package.tracker.Tracker.flush();

let parent = view.parentView;
let parentElement = view._domrange.parentElement;
let next = view._domrange.lastNode().nextSibling;
let nextComment = null;

if (!next) {
// percolate:momentum requires a next node to show the new nodes
next = nextComment = document.createComment('Blaze HMR Placeholder');
parentElement.insertBefore(nextComment, null);
}

if (!parent) {
// TODO: we only need to update a single root view
return updateRootViews();
}

if (view.templateContentBlock) {
overrideTemplateContentBlock = view.templateContentBlock;
}
if (view.templateElseBlock) {
overrideTemplateElseBlock = view.templateElseBlock;
}

// Since there is a parent range, Blaze will not automatically
// detach the dom range.
view._domrange.detach();
view._domrange.destroy();
let newView;

try {
newView = Blaze.render(
Template[view.template.viewName.slice('Template.'.length)],
parentElement,
next,
parent
);
} catch (error) {
console.log('[Blaze HMR] Error re-rending template:');
console.error(error);
lastUpdateFailed = true;
}

let index = parent._domrange.members.findIndex(member => {
return member && member.view === view;
});
if (newView) {
parent._domrange.members.splice(index, 1, newView._domrange);
} else {
parent._domrange.members.splice(index, 1);
}

if (nextComment) {
parentElement.removeChild(nextComment);
}
}
});
});
}
Loading

0 comments on commit 8e1eb8c

Please sign in to comment.