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

Add basic HMR support for Svelte 3 (and svelte-native) #107

Closed
wants to merge 16 commits into from
Closed
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
44 changes: 8 additions & 36 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
const { basename, extname, relative } = require('path');
const { getOptions } = require('loader-utils');
const VirtualModules = require('./lib/virtual');
const posixify = require('./lib/posixify');

const hotApi = require.resolve('./lib/hot-api.js');

const { version } = require('svelte/package.json');
const major_version = +version[0];
const { compile, preprocess } = major_version >= 3
? require('svelte/compiler')
: require('svelte');
const {
major_version,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
major_version,
major_version,
minor_version,

compile,
preprocess,
makeHot,
} = require('./lib/resolve-svelte');

const pluginOptions = {
externalDependencies: true,
Expand All @@ -25,34 +25,6 @@ const pluginOptions = {
markup: true
};

function makeHot(id, code, hotOptions) {
const options = JSON.stringify(hotOptions);
const replacement = `
if (module.hot) {
const { configure, register, reload } = require('${posixify(hotApi)}');

module.hot.accept();

if (!module.hot.data) {
// initial load
configure(${options});
$2 = register(${id}, $2);
} else {
// hot update
$2 = reload(${id}, $2);
}
}

export default $2;
`;

return code.replace(/(export default ([^;]*));/, replacement);
}

function posixify(file) {
return file.replace(/[/\\]/g, '/');
}

function sanitize(input) {
return basename(input)
.replace(extname(input), '')
Expand Down Expand Up @@ -106,7 +78,7 @@ module.exports = function(source, map) {
const virtualModules = virtualModuleInstances.get(this._compiler);

this.cacheable();

const options = Object.assign({}, getOptions(this));
const callback = this.async();

Expand Down
29 changes: 29 additions & 0 deletions lib/make-hot.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
const posixify = require('./posixify');

const hotApi = require.resolve('./hot-api.js');

function makeHot(id, code, hotOptions) {
const options = JSON.stringify(hotOptions);
const replacement = `
if (module.hot) {
const { configure, register, reload } = require('${posixify(hotApi)}');

module.hot.accept();

if (!module.hot.data) {
// initial load
configure(${options});
$2 = register(${id}, $2);
} else {
// hot update
$2 = reload(${id}, $2);
}
}

export default $2;
`;

return code.replace(/(export default ([^;]*));/, replacement);
}

module.exports = makeHot;
3 changes: 3 additions & 0 deletions lib/posixify.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = function posixify(file) {
return file.replace(/[/\\]/g, '/');
};
39 changes: 39 additions & 0 deletions lib/resolve-svelte.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
const path = require('path');

const resolveSvelte = () => {
const { SVELTE } = process.env;

const absolute = (name, base) =>
path.isAbsolute(name) ? name : path.join(base, name);

if (SVELTE) {
return {
req: require,
base: absolute(SVELTE, process.cwd()),
};
} else {
return {
req: require.main.require.bind(require.main),
base: 'svelte',
};
}
};

const { req, base } = resolveSvelte();

const { version } = req(`${base}/package.json`);

const major_version = +version[0];
Copy link

@AlbertMarashi AlbertMarashi May 18, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const major_version = +version[0];
const [major_version, minor_version ]= version.split('.');


const { compile, preprocess } =
major_version >= 3 ? req(`${base}/compiler`) : req(`${base}`);

const makeHot =
major_version >= 3 ? require('./svelte3/make-hot') : require('./make-hot');

module.exports = {
major_version,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
major_version,
major_version,
minor_version,

compile,
preprocess,
makeHot,
};
63 changes: 63 additions & 0 deletions lib/svelte-native/hot/patch-page-show-modal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/* global Symbol */

// This module monkey patches Page#showModal in order to be able to
// access from the HMR proxy data passed to `showModal` in svelte-native.
//
// Data are stored in a opaque prop accessible with `getModalData`.
//
// It also switches the `closeCallback` option with a custom brewed one
// in order to give the proxy control over when its own instance will be
// destroyed.
//
// Obviously this method suffer from extreme coupling with the target code
// in svelte-native. So it would be wise to recheck compatibility on SN
// version upgrades.
//
// Relevant code is there (last checked version):
//
// https://github.com/halfnelson/svelte-native/blob/08702e6b178644f43052f6ec0a789a51e800d21b/src/dom/svelte/StyleElement.ts
//

// FIXME should we override ViewBase#showModal instead?
import { Page } from 'tns-core-modules/ui/page';

const prop =
typeof Symbol !== 'undefined'
? Symbol('hmr_svelte_native_modal')
: '___HMR_SVELTE_NATIVE_MODAL___';

const sup = Page.prototype.showModal;

let patched = false;

export const patchShowModal = () => {
// guard: already patched
if (patched) return;
patched = true;

Page.prototype.showModal = function(modalView, options) {
const modalData = {
originalOptions: options,
closeCallback: options.closeCallback,
};

modalView[prop] = modalData;

// Proxies to a function that can be swapped on the fly by HMR proxy.
//
// The default is still to call the original closeCallback from svelte
// navtive, which will destroy the modal view & component. This way, if
// no HMR happens on the modal content, normal behaviour is preserved
// without the proxy having any work to do.
//
const closeCallback = (...args) => {
return modalData.closeCallback(...args);
};

const temperedOptions = Object.assign({}, options, { closeCallback });

return sup.call(this, modalView, temperedOptions);
};
};

export const getModalData = modalView => modalView[prop];
Loading