Get the webpack dev server to work again

This required triggering the `converse-loaded` event in the entry.js
file, which means it won't be triggered for `@converse/headless` when
used in isolation.

Not ideal, but probably ok because consumers of `@converse/headless`
should probably import it into their own project in any case.
This commit is contained in:
JC Brand 2021-05-06 11:44:16 +02:00
parent d0594a6bfc
commit dd609c1cec
10 changed files with 1418 additions and 1330 deletions

View File

@ -2,9 +2,9 @@
<div id="banner"><a href="https://github.com/jcbrand/converse.js/blob/master/docs/source/theming.rst">Edit me on GitHub</a></div>
============
Dependencies
============
=============================
Starting up a dev environment
=============================
Installing the 3rd party dependencies
=====================================
@ -64,30 +64,12 @@ the list under the ``devDependencies`` in `package.json <https://github.com/jcbr
where you can log in and be taken directly to the chatroom.
Brief description of Converse's dependencies
===============================================
Converse relies on the following dependencies:
* `DayJS <https://github.com/iamkun/dayjs>`_ provides a better API for handling dates and times.
* `Strophe.js <http://strophe.im/>`_ maintains the XMPP session, is used to
build XMPP stanzas, to send them, and to register handlers for received stanzas.
* `lodash <https://lodash.com/>`_ provides very useful utility functions.
* `Skeletor <https://github.com/skeletorjs/skeletor/>`_, a `Backbone <http://backbonejs.org/>`_ fork
which is used to model the data as Models and Collections and to create Views that render the UI.
* `pluggable.js <https://github.com/jcbrand/pluggable.js>`_ provides the plugin
architecture for Converse. It registers and initializes plugins and
allows existing attributes, functions and objects on Converse to be
overridden inside plugins.
.. _`dependency-libsignal`:
Libsignal
---------
If you want OMEMO encryption, you need to load `libsignal
<https://github.com/signalapp/libsignal-protocol-javascript>`_ separately in
your page.
If you want OMEMO encryption, you need to load `libsignal <https://github.com/signalapp/libsignal-protocol-javascript>`_ separately in your page.
For example::

View File

@ -167,7 +167,7 @@ Accessing 3rd party libraries
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Immediately inside the module shown above you can access 3rd party libraries (such
dayjs and lodash) via the ``converse.env`` map.
dayjs) via the ``converse.env`` map.
The code for it could look something like this:
@ -175,7 +175,7 @@ The code for it could look something like this:
// Commonly used utilities and variables can be found under the "env"
// namespace of the "converse" global.
const { Promise, Strophe, dayjs, sizzle, _, $build, $iq, $msg, $pres } = converse.env;
const { Promise, Strophe, dayjs, sizzle, $build, $iq, $msg, $pres } = converse.env;
These dependencies are closured so that they don't pollute the global
namespace, that's why you need to access them in such a way inside the module.
@ -263,7 +263,8 @@ For example:
Overriding a template
~~~~~~~~~~~~~~~~~~~~~
Converse uses various templates, loaded with lodash, to generate its HTML.
Converse uses `lit-html <https://lit-html.polymer-project.org/guide>`_
templates.
It's not possible to override a template with the plugin's ``overrides``
feature, instead you should configure a new path to your own template via your
@ -278,26 +279,6 @@ For example, in your webpack config file, you could add the following to the
.. code-block:: javascript
module: {
{
test: /templates\/.*\.(html|svg)$/,
use: [{
loader: 'lodash-template-webpack-loader',
options: {
escape: /\{\{\{([\s\S]+?)\}\}\}/g,
evaluate: /\{\[([\s\S]+?)\]\}/g,
interpolate: /\{\{([\s\S]+?)\}\}/g,
// By default, template places the values from your data in the
// local scope via the with statement. However, you can specify
// a single variable name with the variable setting. This can
// significantly improve the speed at which a template is able
// to render.
variable: 'o',
prependFilenameComment: __dirname
}
}]
}
},
resolve: {
extensions: ['.js'],
modules: [
@ -305,17 +286,13 @@ For example, in your webpack config file, you could add the following to the
path.join(__dirname, 'node_modules/converse.js/src')
],
alias: {
'templates/profile_view.html$': path.resolve(__dirname, 'templates/profile_view.html')
'plugins/profile/templates/profile.js$': path.resolve(__dirname, 'templates/custom-profile.js')
}
}
You'll need to install ``lodash-template-webpack-loader``.
Currently Converse uses a fork of `lodash-template-webpack-loader <https://github.com/jcbrand/lodash-template-webpack-loader>`_.
To install it, you can add ``"lodash-template-webpack-loader": "jcbrand/lodash-template-webpack-loader"``
to your package.json's ``devDependencies``.
This will override the template that gets imported at the path ``plugins/profile/templates/profile.js``
with your own template at the path ``templates/custom-profile.js`` (relative to
your webpack config file).
.. _`dependencies`:

1349
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -83,7 +83,7 @@
"exports-loader": "^0.7.0",
"fast-text-encoding": "^1.0.3",
"file-loader": "^6.0.0",
"html-webpack-plugin": "^4.3.0",
"html-webpack-plugin": "^5.3.1",
"http-server": "^0.12.3",
"imports-loader": "^0.8.0",
"install": "^0.13.0",
@ -110,8 +110,8 @@
"sinon": "^9.2.4",
"style-loader": "^0.23.1",
"webpack": "^5.36.1",
"webpack-cli": "^4.5.0",
"webpack-dev-server": "^3.11.2",
"webpack-cli": "^4.6.0",
"webpack-dev-server": "^4.0.0-beta.2",
"webpack-merge": "^5.7.3"
},
"dependencies": {

View File

@ -1,10 +1,10 @@
/*global converse */
const mock = {};
window.mock = mock;
let _converse, initConverse;
let _converse;
const converseLoaded = new Promise(resolve => window.addEventListener('converse-loaded', resolve));
const converse = window.converse;
converse.load();
const { u, sizzle, Strophe, dayjs, $iq, $msg, $pres } = converse.env;
jasmine.DEFAULT_TIMEOUT_INTERVAL = 7000;
@ -25,7 +25,6 @@ mock.initConverse = function (promise_names=[], settings=null, func) {
}
document.title = "Converse Tests";
await converseLoaded;
await initConverse(settings);
await Promise.all((promise_names || []).map(_converse.api.waitUntil));
try {
@ -38,10 +37,7 @@ mock.initConverse = function (promise_names=[], settings=null, func) {
}
};
window.addEventListener('converse-loaded', () => {
const { u, sizzle, Strophe, dayjs, $iq, $msg, $pres } = converse.env;
mock.waitUntilDiscoConfirmed = async function (_converse, entity_jid, identities, features=[], items=[], type='info') {
mock.waitUntilDiscoConfirmed = async function (_converse, entity_jid, identities, features=[], items=[], type='info') {
const sel = `iq[to="${entity_jid}"] query[xmlns="http://jabber.org/protocol/disco#${type}"]`;
const iq = await u.waitUntil(() => _converse.connection.IQ_stanzas.filter(iq => sizzle(sel, iq).length).pop(), 300);
const stanza = $iq({
@ -55,9 +51,9 @@ window.addEventListener('converse-loaded', () => {
features?.forEach(feature => stanza.c('feature', {'var': feature}).up());
items?.forEach(item => stanza.c('item', {'jid': item}).up());
_converse.connection._dataRecv(mock.createRequest(stanza));
}
}
mock.createRequest = function (iq) {
mock.createRequest = function (iq) {
iq = typeof iq.tree == "function" ? iq.tree() : iq;
var req = new Strophe.Request(iq, function() {});
req.getResponse = function () {
@ -66,13 +62,13 @@ window.addEventListener('converse-loaded', () => {
return env;
};
return req;
};
};
mock.closeAllChatBoxes = function (_converse) {
mock.closeAllChatBoxes = function (_converse) {
return Promise.all(_converse.chatboxviews.map(view => view.close()));
};
};
mock.toggleControlBox = function () {
mock.toggleControlBox = function () {
const toggle = document.querySelector(".toggle-controlbox");
if (!u.isVisible(document.querySelector("#controlbox"))) {
if (!u.isVisible(toggle)) {
@ -80,25 +76,25 @@ window.addEventListener('converse-loaded', () => {
}
toggle.click();
}
}
}
mock.openControlBox = async function (_converse) {
mock.openControlBox = async function (_converse) {
const model = await _converse.api.controlbox.open();
await u.waitUntil(() => model.get('connected'));
mock.toggleControlBox();
return this;
};
};
mock.closeControlBox = function () {
mock.closeControlBox = function () {
const controlbox = document.querySelector("#controlbox");
if (u.isVisible(controlbox)) {
const button = controlbox.querySelector(".close-chatbox-button");
(button !== null) && button.click();
}
return this;
};
};
mock.waitUntilBookmarksReturned = async function (_converse, bookmarks=[]) {
mock.waitUntilBookmarksReturned = async function (_converse, bookmarks=[]) {
await mock.waitUntilDiscoConfirmed(
_converse, _converse.bare_jid,
[{'category': 'pubsub', 'type': 'pep'}],
@ -125,22 +121,22 @@ window.addEventListener('converse-loaded', () => {
});
_converse.connection._dataRecv(mock.createRequest(stanza));
await _converse.api.waitUntil('bookmarksInitialized');
};
};
mock.openChatBoxes = function (converse, amount) {
mock.openChatBoxes = function (converse, amount) {
for (let i=0; i<amount; i++) {
const jid = mock.cur_names[i].replace(/ /g,'.').toLowerCase() + '@montague.lit';
converse.roster.get(jid).openChat();
}
};
};
mock.openChatBoxFor = async function (_converse, jid) {
mock.openChatBoxFor = async function (_converse, jid) {
await _converse.api.waitUntil('rosterContactsFetched');
_converse.roster.get(jid).openChat();
return u.waitUntil(() => _converse.chatboxviews.get(jid), 1000);
};
};
mock.openChatRoomViaModal = async function (_converse, jid, nick='') {
mock.openChatRoomViaModal = async function (_converse, jid, nick='') {
// Opens a new chatroom
const model = await _converse.api.controlbox.open('controlbox');
await u.waitUntil(() => model.get('connected'));
@ -156,13 +152,13 @@ window.addEventListener('converse-loaded', () => {
modal.el.querySelector('form input[type="submit"]').click();
await u.waitUntil(() => _converse.chatboxviews.get(jid), 1000);
return _converse.chatboxviews.get(jid);
};
};
mock.openChatRoom = function (_converse, room, server) {
mock.openChatRoom = function (_converse, room, server) {
return _converse.api.rooms.open(`${room}@${server}`);
};
};
mock.getRoomFeatures = async function (_converse, muc_jid, features=[]) {
mock.getRoomFeatures = async function (_converse, muc_jid, features=[]) {
const room = Strophe.getNodeFromJid(muc_jid);
muc_jid = muc_jid.toLowerCase();
const stanzas = _converse.connection.IQ_stanzas;
@ -193,10 +189,10 @@ window.addEventListener('converse-loaded', () => {
.c('field', {'type':'text-single', 'var':'muc#roominfo_occupants', 'label':'Number of occupants'})
.c('value').t(0);
_converse.connection._dataRecv(mock.createRequest(features_stanza));
};
};
mock.waitForReservedNick = async function (_converse, muc_jid, nick) {
mock.waitForReservedNick = async function (_converse, muc_jid, nick) {
const stanzas = _converse.connection.IQ_stanzas;
const selector = `iq[to="${muc_jid.toLowerCase()}"] query[node="x-roomuser-item"]`;
const iq = await u.waitUntil(() => stanzas.filter(s => sizzle(selector, s).length).pop());
@ -219,10 +215,10 @@ window.addEventListener('converse-loaded', () => {
if (nick) {
return u.waitUntil(() => nick);
}
};
};
mock.returnMemberLists = async function (_converse, muc_jid, members=[], affiliations=['member', 'owner', 'admin']) {
mock.returnMemberLists = async function (_converse, muc_jid, members=[], affiliations=['member', 'owner', 'admin']) {
if (affiliations.length === 0) {
return;
}
@ -288,9 +284,9 @@ window.addEventListener('converse-loaded', () => {
_converse.connection._dataRecv(mock.createRequest(owner_list_stanza));
}
return new Promise(resolve => _converse.api.listen.on('membersFetched', resolve));
};
};
mock.receiveOwnMUCPresence = async function (_converse, muc_jid, nick) {
mock.receiveOwnMUCPresence = async function (_converse, muc_jid, nick) {
const sent_stanzas = _converse.connection.sent_stanzas;
await u.waitUntil(() => sent_stanzas.filter(iq => sizzle('presence history', iq).length).pop());
const presence = $pres({
@ -305,10 +301,10 @@ window.addEventListener('converse-loaded', () => {
}).up()
.c('status').attrs({code:'110'});
_converse.connection._dataRecv(mock.createRequest(presence));
};
};
mock.openAndEnterChatRoom = async function (_converse, muc_jid, nick, features=[], members=[], force_open=true, settings={}) {
mock.openAndEnterChatRoom = async function (_converse, muc_jid, nick, features=[], members=[], force_open=true, settings={}) {
const { api } = _converse;
muc_jid = muc_jid.toLowerCase();
const room_creation_promise = api.rooms.open(muc_jid, settings, force_open);
@ -327,9 +323,9 @@ window.addEventListener('converse-loaded', () => {
const all_affiliations = Array.isArray(affs) ? affs : (affs ? ['member', 'admin', 'owner'] : []);
await mock.returnMemberLists(_converse, muc_jid, members, all_affiliations);
return model.messages.fetched;
};
};
mock.createContact = async function (_converse, name, ask, requesting, subscription) {
mock.createContact = async function (_converse, name, ask, requesting, subscription) {
const jid = name.replace(/ /g,'.').toLowerCase() + '@montague.lit';
if (_converse.roster.get(jid)) {
return Promise.resolve();
@ -344,9 +340,9 @@ window.addEventListener('converse-loaded', () => {
}, {success, error});
});
return contact;
};
};
mock.createContacts = async function (_converse, type, length) {
mock.createContacts = async function (_converse, type, length) {
/* Create current (as opposed to requesting or pending) contacts
* for the user's roster.
*
@ -379,9 +375,9 @@ window.addEventListener('converse-loaded', () => {
}
const promises = names.slice(0, length).map(n => this.createContact(_converse, n, ask, requesting, subscription));
await Promise.all(promises);
};
};
mock.waitForRoster = async function (_converse, type='current', length=-1, include_nick=true, grouped=true) {
mock.waitForRoster = async function (_converse, type='current', length=-1, include_nick=true, grouped=true) {
const s = `iq[type="get"] query[xmlns="${Strophe.NS.ROSTER}"]`;
const iq = await u.waitUntil(() => _converse.connection.IQ_stanzas.filter(iq => sizzle(s, iq).length).pop());
@ -421,9 +417,9 @@ window.addEventListener('converse-loaded', () => {
}
_converse.connection._dataRecv(mock.createRequest(result));
await _converse.api.waitUntil('rosterContactsFetched');
};
};
mock.createChatMessage = function (_converse, sender_jid, message) {
mock.createChatMessage = function (_converse, sender_jid, message) {
return $msg({
from: sender_jid,
to: _converse.connection.jid,
@ -433,9 +429,9 @@ window.addEventListener('converse-loaded', () => {
.c('body').t(message).up()
.c('markable', {'xmlns': Strophe.NS.MARKERS}).up()
.c('active', {'xmlns': Strophe.NS.CHATSTATES}).tree();
}
}
mock.sendMessage = async function (view, message) {
mock.sendMessage = async function (view, message) {
const promise = new Promise(resolve => view.model.messages.once('rendered', resolve));
const textarea = await u.waitUntil(() => view.querySelector('.chat-textarea'));
textarea.value = message;
@ -446,10 +442,10 @@ window.addEventListener('converse-loaded', () => {
keyCode: 13
});
return promise;
};
};
window.libsignal = {
window.libsignal = {
'SignalProtocolAddress': function (name, device_id) {
this.name = name;
this.deviceId = device_id;
@ -504,9 +500,9 @@ window.addEventListener('converse-loaded', () => {
});
}
}
};
};
mock.default_muc_features = [
mock.default_muc_features = [
'http://jabber.org/protocol/muc',
'jabber:iq:register',
Strophe.NS.SID,
@ -517,18 +513,18 @@ window.addEventListener('converse-loaded', () => {
'muc_open',
'muc_unmoderated',
'muc_anonymous'
];
];
mock.view_mode = 'overlayed';
mock.view_mode = 'overlayed';
// Names from http://www.fakenamegenerator.com/
mock.req_names = [
// Names from http://www.fakenamegenerator.com/
mock.req_names = [
'Escalus, prince of Verona', 'The Nurse', 'Paris'
];
mock.pend_names = [
];
mock.pend_names = [
'Lord Capulet', 'Guard', 'Servant'
];
mock.current_contacts_map = {
];
mock.current_contacts_map = {
'Mercutio': ['Colleagues', 'friends & acquaintences'],
'Juliet Capulet': ['friends & acquaintences'],
'Lady Montague': ['Colleagues', 'Family'],
@ -544,52 +540,52 @@ window.addEventListener('converse-loaded', () => {
'Gregory': ['friends & acquaintences'],
'Potpan': [],
'Friar John': []
};
};
const map = mock.current_contacts_map;
const groups_map = {};
Object.keys(map).forEach(k => {
const map = mock.current_contacts_map;
const groups_map = {};
Object.keys(map).forEach(k => {
const groups = map[k].length ? map[k] : ["Ungrouped"];
Object.values(groups).forEach(g => {
groups_map[g] = groups_map[g] ? [...groups_map[g], k] : [k]
});
});
mock.groups_map = groups_map;
});
mock.groups_map = groups_map;
mock.cur_names = Object.keys(mock.current_contacts_map);
mock.num_contacts = mock.req_names.length + mock.pend_names.length + mock.cur_names.length;
mock.cur_names = Object.keys(mock.current_contacts_map);
mock.num_contacts = mock.req_names.length + mock.pend_names.length + mock.cur_names.length;
mock.groups = {
mock.groups = {
'colleagues': 3,
'friends & acquaintences': 3,
'Family': 4,
'ænemies': 3,
'Ungrouped': 2
};
};
mock.chatroom_names = [
mock.chatroom_names = [
'Dyon van de Wege',
'Thomas Kalb',
'Dirk Theissen',
'Felix Hofmann',
'Ka Lek',
'Anne Ebersbacher'
];
// TODO: need to also test other roles and affiliations
mock.chatroom_roles = {
];
// TODO: need to also test other roles and affiliations
mock.chatroom_roles = {
'Anne Ebersbacher': { affiliation: "owner", role: "moderator" },
'Dirk Theissen': { affiliation: "admin", role: "moderator" },
'Dyon van de Wege': { affiliation: "member", role: "occupant" },
'Felix Hofmann': { affiliation: "member", role: "occupant" },
'Ka Lek': { affiliation: "member", role: "occupant" },
'Thomas Kalb': { affiliation: "member", role: "occupant" }
};
};
mock.event = {
mock.event = {
'preventDefault': function () {}
};
};
function clearIndexedDB () {
function clearIndexedDB () {
const promise = u.getOpenPromise();
const db_request = window.indexedDB.open("converse-test-persistent");
db_request.onsuccess = function () {
@ -609,17 +605,17 @@ window.addEventListener('converse-loaded', () => {
return promise.reject(ev.target.error);
}
return promise;
}
}
function clearStores () {
function clearStores () {
[localStorage, sessionStorage].forEach(
s => Object.keys(s).forEach(k => k.match(/^converse-test-/) && s.removeItem(k))
);
const cache_key = `converse.room-bookmarksromeo@montague.lit`;
window.sessionStorage.removeItem(cache_key+'fetched');
}
}
initConverse = async (settings) => {
const initConverse = async (settings) => {
clearStores();
await clearIndexedDB();
@ -675,7 +671,4 @@ window.addEventListener('converse-loaded', () => {
}
window.converse_disable_effects = true;
return _converse;
}
});
converse.load();
}

View File

@ -64,4 +64,15 @@ const converse = {
}
window.converse = converse;
/**
* Once Converse.js has loaded, it'll dispatch a custom event with the name `converse-loaded`.
* You can listen for this event in order to be informed as soon as converse.js has been
* loaded and parsed, which would mean it's safe to call `converse.initialize`.
* @event converse-loaded
* @example window.addEventListener('converse-loaded', () => converse.initialize());
*/
const ev = new CustomEvent('converse-loaded', {'detail': { converse }});
window.dispatchEvent(ev);
export default converse;

View File

@ -1419,13 +1419,3 @@ Object.assign(converse, {
u,
}
});
/**
* Once Converse.js has loaded, it'll dispatch a custom event with the name `converse-loaded`.
* You can listen for this event in order to be informed as soon as converse.js has been
* loaded and parsed, which would mean it's safe to call `converse.initialize`.
* @event converse-loaded
* @example window.addEventListener('converse-loaded', () => converse.initialize());
*/
const ev = new CustomEvent('converse-loaded', {'detail': { converse }});
window.dispatchEvent(ev);

View File

@ -9,10 +9,10 @@
<script src="3rdparty/libsignal-protocol.js"></script>
<link rel="manifest" href="./manifest.json">
<link rel="shortcut icon" type="image/ico" href="favicon.ico"/>
<script src="https://cdn.conversejs.org/3rdparty/libsignal-protocol.min.js"></script>
</head>
<body class="reset"></body>
<script>
window.addEventListener('converse-loaded', () => {
converse.plugins.add('converse-debug', {
initialize () {
const { _converse } = this;
@ -41,5 +41,6 @@
muc_show_logs_before_join: true,
whitelisted_plugins: ['converse-debug', 'converse-batched-probe'],
});
});
</script>
</html>

View File

@ -5,7 +5,7 @@ const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const common = require("./webpack.common.js");
const path = require('path');
const webpack = require('webpack');
const { merge} = require("webpack-merge");
const { merge } = require("webpack-merge");
const plugins = [
new MiniCssExtractPlugin({filename: '../dist/converse.min.css'}),

View File

@ -1,18 +1,21 @@
/* global module */
/* global module, __dirname */
const HTMLWebpackPlugin = require('html-webpack-plugin');
const common = require("./webpack.common.js");
const { merge } = require("webpack-merge");
const path = require("path");
module.exports = merge(common, {
mode: "development",
devtool: "inline-source-map",
devServer: {
contentBase: "./"
static: [ path.resolve(__dirname, '') ],
port: 3003
},
plugins: [
new HTMLWebpackPlugin({
title: 'Converse.js Dev',
template: 'webpack.html'
template: 'webpack.html',
filename: 'index.html'
})
],
});