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

Support jsbundling #44

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
115 changes: 55 additions & 60 deletions app/assets/javascripts/hotwire_spark.js
Original file line number Diff line number Diff line change
Expand Up @@ -1396,71 +1396,65 @@ var HotwireSpark = (function () {
}

class StimulusReloader {
static async reload(filePattern) {
const document = await reloadHtmlDocument();
return new StimulusReloader(document, filePattern).reload();
static async reload(path) {
return new StimulusReloader(path).reload();
}
constructor(document) {
let filePattern = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : /./;
this.document = document;
this.filePattern = filePattern;
static async reloadAll() {
Stimulus.controllers.forEach(controller => {
Stimulus.unload(controller.identifier);
Stimulus.register(controller.identifier, controller.constructor);
});
return Promise.resolve();
}
constructor(changedPath) {
this.changedPath = changedPath;
this.application = window.Stimulus;
}
async reload() {
log("Reload Stimulus controllers...");
this.application.stop();
await this.#reloadChangedStimulusControllers();
this.#unloadDeletedStimulusControllers();
try {
await this.#reloadChangedController();
} catch (error) {
if (error instanceof SourceFileNotFound) {
this.#deregisterChangedController();
} else {
console.error("Error reloading controller", error);
}
}
this.application.start();
}
async #reloadChangedStimulusControllers() {
await Promise.all(this.#stimulusControllerPathsToReload.map(async moduleName => this.#reloadStimulusController(moduleName)));
}
get #stimulusControllerPathsToReload() {
this.controllerPathsToReload = this.controllerPathsToReload || this.#stimulusControllerPaths.filter(path => this.#shouldReloadController(path));
return this.controllerPathsToReload;
}
get #stimulusControllerPaths() {
return Object.keys(this.#stimulusPathsByModule).filter(path => path.endsWith("_controller"));
}
#shouldReloadController(path) {
return this.filePattern.test(path);
async #reloadChangedController() {
const module = await this.#importControllerFromSource(this.changedPath);
await this.#registerController(this.#changedControllerIdentifier, module);
}
get #stimulusPathsByModule() {
this.pathsByModule = this.pathsByModule || this.#parseImportmapJson();
return this.pathsByModule;
}
#parseImportmapJson() {
const importmapScript = this.document.querySelector("script[type=importmap]");
return JSON.parse(importmapScript.text).imports;
}
async #reloadStimulusController(moduleName) {
log(`\t${moduleName}`);
const controllerName = this.#extractControllerName(moduleName);
const path = cacheBustedUrl(this.#pathForModuleName(moduleName));
const module = await import(path);
this.#registerController(controllerName, module);
}
#unloadDeletedStimulusControllers() {
this.#controllersToUnload.forEach(controller => this.#deregisterController(controller.identifier));
}
get #controllersToUnload() {
if (this.#didChangeTriggerAReload) {
return [];
} else {
return this.application.controllers.filter(controller => this.filePattern.test(`${controller.identifier}_controller`));
async #importControllerFromSource(path) {
const response = await fetch(`/spark/source_files/?path=${path}`);
if (response.status === 404) {
console.debug("EN SERIO", response.status, "path", path);
throw new SourceFileNotFound(`Source file not found: ${path}`);
}
const sourceCode = await response.text();
const blob = new Blob([sourceCode], {
type: "application/javascript"
});
const moduleUrl = URL.createObjectURL(blob);
const module = await import(moduleUrl);
URL.revokeObjectURL(moduleUrl);
return module;
}
get #didChangeTriggerAReload() {
return this.#stimulusControllerPathsToReload.length > 0;
}
#pathForModuleName(moduleName) {
return this.#stimulusPathsByModule[moduleName];
get #changedControllerIdentifier() {
this.changedControllerIdentifier = this.changedControllerIdentifier || this.#extractControllerName(this.changedPath);
return this.changedControllerIdentifier;
}
#extractControllerName(path) {
return path.replace(/^.*\//, "").replace("_controller", "").replace(/\//g, "--").replace(/_/g, "-");
return path.replace(/^.*\//, "").replace("_controller", "").replace(/\//g, "--").replace(/_/g, "-").replace(/\.js$/, "");
}
#deregisterChangedController() {
this.#deregisterController(this.#changedControllerIdentifier);
}
#registerController(name, module) {
log("\tReloading controller", name);
this.application.unload(name);
this.application.register(name, module.default);
}
Expand All @@ -1469,14 +1463,15 @@ var HotwireSpark = (function () {
this.application.unload(name);
}
}
class SourceFileNotFound extends Error {}

class HtmlReloader {
static async reload() {
return new HtmlReloader().reload();
}
async reload() {
const reloadedDocument = await this.#reloadHtml();
await this.#reloadStimulus(reloadedDocument);
await this.#reloadHtml();
await this.#reloadStimulus();
}
async #reloadHtml() {
log("Reload html...");
Expand All @@ -1487,8 +1482,8 @@ var HotwireSpark = (function () {
#updateBody(newBody) {
Idiomorph.morph(document.body, newBody);
}
async #reloadStimulus(reloadedDocument) {
return new StimulusReloader(reloadedDocument).reload();
async #reloadStimulus() {
await StimulusReloader.reloadAll();
}
}

Expand Down Expand Up @@ -1566,26 +1561,26 @@ var HotwireSpark = (function () {
action,
path
} = _ref;
const fileName = assetNameFromPath(path);
switch (action) {
case "reload_html":
return this.reloadHtml();
case "reload_css":
return this.reloadCss(fileName);
return this.reloadCss(path);
case "reload_stimulus":
return this.reloadStimulus(fileName);
return this.reloadStimulus(path);
default:
throw new Error(`Unknown action: ${action}`);
}
},
reloadHtml() {
return HtmlReloader.reload();
},
reloadCss(fileName) {
reloadCss(path) {
const fileName = assetNameFromPath(path);
return CssReloader.reload(new RegExp(fileName));
},
reloadStimulus(fileName) {
return StimulusReloader.reload(new RegExp(fileName));
reloadStimulus(path) {
return StimulusReloader.reload(path);
}
});

Expand Down
2 changes: 1 addition & 1 deletion app/assets/javascripts/hotwire_spark.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion app/assets/javascripts/hotwire_spark.min.js.map

Large diffs are not rendered by default.

14 changes: 14 additions & 0 deletions app/controllers/hotwire/spark/source_files_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
class Hotwire::Spark::SourceFilesController < ActionController::Base
def show
if File.exist?(path_param)
render plain: File.read(path_param)
else
head :not_found
end
end

private
def path_param
Rails.root.join params[:path]
end
end
13 changes: 6 additions & 7 deletions app/javascript/hotwire/spark/channels/monitoring_channel.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,13 @@ consumer.subscriptions.create({ channel: "Hotwire::Spark::Channel" }, {
},

dispatch({ action, path }) {
const fileName = assetNameFromPath(path)

switch(action) {
case "reload_html":
return this.reloadHtml()
case "reload_css":
return this.reloadCss(fileName)
return this.reloadCss(path)
case "reload_stimulus":
return this.reloadStimulus(fileName)
return this.reloadStimulus(path)
default:
throw new Error(`Unknown action: ${action}`)
}
Expand All @@ -36,12 +34,13 @@ consumer.subscriptions.create({ channel: "Hotwire::Spark::Channel" }, {
return HtmlReloader.reload()
},

reloadCss(fileName) {
reloadCss(path) {
const fileName = assetNameFromPath(path)
return CssReloader.reload(new RegExp(fileName))
},

reloadStimulus(fileName) {
return StimulusReloader.reload(new RegExp(fileName))
reloadStimulus(path) {
return StimulusReloader.reload(path)
}
})

8 changes: 4 additions & 4 deletions app/javascript/hotwire/spark/reloaders/html_reloader.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ export class HtmlReloader {
}

async reload() {
const reloadedDocument = await this.#reloadHtml()
await this.#reloadStimulus(reloadedDocument)
await this.#reloadHtml()
await this.#reloadStimulus()
}

async #reloadHtml() {
Expand All @@ -25,7 +25,7 @@ export class HtmlReloader {
Idiomorph.morph(document.body, newBody)
}

async #reloadStimulus(reloadedDocument) {
return new StimulusReloader(reloadedDocument).reload()
async #reloadStimulus() {
await StimulusReloader.reloadAll()
}
}
108 changes: 49 additions & 59 deletions app/javascript/hotwire/spark/reloaders/stimulus_reloader.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
import { log } from "../logger.js"
import { cacheBustedUrl, reloadHtmlDocument } from "../helpers.js"

export class StimulusReloader {
static async reload(filePattern) {
const document = await reloadHtmlDocument()
return new StimulusReloader(document, filePattern).reload()
static async reload(path) {
return new StimulusReloader(path).reload()
}

constructor(document, filePattern = /./) {
this.document = document
this.filePattern = filePattern
static async reloadAll() {
Stimulus.controllers.forEach(controller => {
Stimulus.unload(controller.identifier)
Stimulus.register(controller.identifier, controller.constructor)
})

return Promise.resolve()
}

constructor(changedPath) {
this.changedPath = changedPath
this.application = window.Stimulus
}

Expand All @@ -18,70 +24,45 @@ export class StimulusReloader {

this.application.stop()

await this.#reloadChangedStimulusControllers()
this.#unloadDeletedStimulusControllers()
try {
await this.#reloadChangedController()
}
catch(error) {
if (error instanceof SourceFileNotFound) {
this.#deregisterChangedController()
} else {
console.error("Error reloading controller", error)
}
}

this.application.start()
}

async #reloadChangedStimulusControllers() {
await Promise.all(
this.#stimulusControllerPathsToReload.map(async moduleName => this.#reloadStimulusController(moduleName))
)
}

get #stimulusControllerPathsToReload() {
this.controllerPathsToReload = this.controllerPathsToReload || this.#stimulusControllerPaths.filter(path => this.#shouldReloadController(path))
return this.controllerPathsToReload
}

get #stimulusControllerPaths() {
return Object.keys(this.#stimulusPathsByModule).filter(path => path.endsWith("_controller"))
}

#shouldReloadController(path) {
return this.filePattern.test(path)
}

get #stimulusPathsByModule() {
this.pathsByModule = this.pathsByModule || this.#parseImportmapJson()
return this.pathsByModule
async #reloadChangedController() {
const module = await this.#importControllerFromSource(this.changedPath)
await this.#registerController(this.#changedControllerIdentifier, module)
}

#parseImportmapJson() {
const importmapScript = this.document.querySelector("script[type=importmap]")
return JSON.parse(importmapScript.text).imports
}

async #reloadStimulusController(moduleName) {
log(`\t${moduleName}`)
async #importControllerFromSource(path) {
const response = await fetch(`/spark/source_files/?path=${path}`)

const controllerName = this.#extractControllerName(moduleName)
const path = cacheBustedUrl(this.#pathForModuleName(moduleName))
if (response.status === 404) {
throw new SourceFileNotFound(`Source file not found: ${path}`)
}

const module = await import(path)
const sourceCode = await response.text()

this.#registerController(controllerName, module)
}
const blob = new Blob([sourceCode], { type: "application/javascript" })
const moduleUrl = URL.createObjectURL(blob)
const module = await import(moduleUrl)
URL.revokeObjectURL(moduleUrl)

#unloadDeletedStimulusControllers() {
this.#controllersToUnload.forEach(controller => this.#deregisterController(controller.identifier))
}

get #controllersToUnload() {
if (this.#didChangeTriggerAReload) {
return []
} else {
return this.application.controllers.filter(controller => this.filePattern.test(`${controller.identifier}_controller`))
}
return module
}

get #didChangeTriggerAReload() {
return this.#stimulusControllerPathsToReload.length > 0
}

#pathForModuleName(moduleName) {
return this.#stimulusPathsByModule[moduleName]
get #changedControllerIdentifier() {
this.changedControllerIdentifier = this.changedControllerIdentifier || this.#extractControllerName(this.changedPath)
return this.changedControllerIdentifier
}

#extractControllerName(path) {
Expand All @@ -90,9 +71,16 @@ export class StimulusReloader {
.replace("_controller", "")
.replace(/\//g, "--")
.replace(/_/g, "-")
.replace(/\.js$/, "")
}

#deregisterChangedController() {
this.#deregisterController(this.#changedControllerIdentifier)
}

#registerController(name, module) {
log("\tReloading controller", name)

this.application.unload(name)
this.application.register(name, module.default)
}
Expand All @@ -102,3 +90,5 @@ export class StimulusReloader {
this.application.unload(name)
}
}

class SourceFileNotFound extends Error { }
Loading
Loading