MM-67323 Add system for plugins to use shared package and allow plugins to load asynchronously (#35183)

* Remove jest-junit and unignore build folder in web app packages

We don't actually use the file output by jest-junit, and I don't think we
have since we moved off of Jenkins for CI

* Move parcel-namer-shared into build folder

* MM-67323 Add loadSharedDependency API and script for plugins to use it

* Fix client and mattermost-redux packages missing const enums

* Change interface for webAppExternals
This commit is contained in:
Harrison Healey 2026-02-17 12:57:49 -05:00 committed by GitHub
parent 28406fbe23
commit 2da1e56e6c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 60 additions and 55 deletions

2
webapp/.gitignore vendored
View file

@ -13,4 +13,4 @@ platform/*/dist
platform/*/lib
config.override.mk
build
build/test-results*.xml

View file

@ -20,10 +20,6 @@ const config = {
'/node_modules/',
'src/packages/mattermost-redux/',
],
reporters: [
'default',
['jest-junit', {outputDirectory: 'build', outputName: 'test-results-channels.xml'}],
],
};
module.exports = config;

View file

@ -32,10 +32,6 @@ const config = {
},
moduleDirectories: ['src', 'node_modules'],
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'],
reporters: [
'default',
['jest-junit', {outputDirectory: 'build', outputName: 'test-results.xml'}],
],
transformIgnorePatterns: [
'node_modules/(?!react-native|react-router|pdfjs-dist|p-queue|p-timeout|@mattermost/compass-icons|cidr-regex|ip-regex|serialize-error)',
],

View file

@ -18,10 +18,6 @@ const config = {
'/node_modules/',
'src/packages/mattermost-redux/src/selectors/create_selector',
],
reporters: [
'default',
['jest-junit', {outputDirectory: 'build', outputName: 'test-results-mattermost-redux.xml'}],
],
};
module.exports = config;

View file

@ -152,7 +152,6 @@
"jest-canvas-mock": "2.5.0",
"jest-cli": "30.1.3",
"jest-environment-jsdom": "30.1.0",
"jest-junit": "16.0.0",
"jest-watch-typeahead": "3.0.1",
"nock": "13.2.8",
"node-fetch": "2.7.0",

View file

@ -30,6 +30,7 @@ import {useWebSocket, useWebSocketClient, WebSocketContext} from 'utils/use_webs
import {imageURLForUser} from 'utils/utils';
import {openInteractiveDialog} from './interactive_dialog'; // This import has intentional side effects. Do not remove without research.
import {loadSharedDependency} from './shared_dependencies';
import Textbox from './textbox';
// Note: We can't directly use the hook here, but we can create a function that opens the external pricing page
@ -73,6 +74,7 @@ interface WindowWithLibraries {
canPopout: typeof canPopout;
};
};
loadSharedDependency(request: string): unknown;
openPricingModal: () => void;
Components: {
Textbox: typeof Textbox;
@ -150,6 +152,7 @@ window.WebappUtils = {
canPopout,
},
};
window.loadSharedDependency = loadSharedDependency;
// For plugins, we provide a simple function that always tries to open the external pricing page
// This won't respect air-gapped status, but plugins shouldn't be calling this in air-gapped environments

View file

@ -0,0 +1,16 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Every module exported from the @mattermost/shared package must be added to this map
const sharedDependencies = new Map([
['@mattermost/shared/components/emoji', () => import('@mattermost/shared/components/emoji')],
]);
export function loadSharedDependency(request: string) {
const loader = sharedDependencies.get(request);
if (loader) {
return loader();
}
throw new Error(`A plugin attempted to load ${request} which couldn't be found.`);
}

View file

@ -206,7 +206,6 @@
"jest-canvas-mock": "2.5.0",
"jest-cli": "30.1.3",
"jest-environment-jsdom": "30.1.0",
"jest-junit": "16.0.0",
"jest-watch-typeahead": "3.0.1",
"nock": "13.2.8",
"node-fetch": "2.7.0",
@ -17105,39 +17104,6 @@
"fsevents": "^2.3.3"
}
},
"node_modules/jest-junit": {
"version": "16.0.0",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"mkdirp": "^1.0.4",
"strip-ansi": "^6.0.1",
"uuid": "^8.3.2",
"xml": "^1.0.1"
},
"engines": {
"node": ">=10.12.0"
}
},
"node_modules/jest-junit/node_modules/strip-ansi": {
"version": "6.0.1",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/jest-junit/node_modules/uuid": {
"version": "8.3.2",
"dev": true,
"license": "MIT",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/jest-leak-detector": {
"version": "30.1.0",
"dev": true,
@ -19171,8 +19137,8 @@
},
"node_modules/mkdirp": {
"version": "1.0.4",
"devOptional": true,
"license": "MIT",
"optional": true,
"bin": {
"mkdirp": "bin/cmd.js"
},
@ -25788,11 +25754,6 @@
"xtend": "^4.0.0"
}
},
"node_modules/xml": {
"version": "1.0.1",
"dev": true,
"license": "MIT"
},
"node_modules/xml-name-validator": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",

View file

@ -11,6 +11,7 @@
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"jsx": "react",
"preserveConstEnums": true,
"outDir": "./lib",
"rootDir": "./src",
"composite": true,

View file

@ -14,6 +14,7 @@
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"jsx": "react",
"preserveConstEnums": true,
"outDir": "./lib",
"rootDir": "../../channels/src/packages/mattermost-redux/src",
"composite": true,

View file

@ -1,5 +1,5 @@
{
"extends": "@parcel/config-default",
"bundler": "@parcel/bundler-library",
"namers": ["./parcel-namer-shared", "..."]
"namers": ["./build/parcel-namer-shared", "..."]
}

View file

@ -0,0 +1,31 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// This script is intended to be used by Mattermost plugins to set up their Webpack externals to share their
// dependencies with the web app. It includes both third party dependencies (React, etc) and the MM Shared package.
const windowExternals = {
react: 'React',
'react-dom': 'ReactDOM',
redux: 'Redux',
luxon: 'Luxon',
'react-redux': 'ReactRedux',
'prop-types': 'PropTypes',
'react-bootstrap': 'ReactBootstrap',
'react-router-dom': 'ReactRouterDom',
'react-intl': 'ReactIntl',
};
function webAppExternals() {
return [
windowExternals,
({request}, callback) => {
if ((/^@mattermost\/shared\//).test(request)) {
return callback(null, `promise globalThis.loadSharedDependency('${request}')`);
}
return callback();
},
];
}
module.exports = webAppExternals;

View file

@ -8,6 +8,7 @@
"homepage": "https://github.com/mattermost/mattermost/tree/master/webapp/platform/shared#readme",
"license": "MIT",
"files": [
"build/webpack-web-app-externals.cjs",
"dist",
"src"
],
@ -32,6 +33,10 @@
"import": "./dist/module.js",
"require": "./dist/main.js"
},
"./build/webpack-web-app-externals": {
"source": "./build/webpack-web-app-externals.cjs",
"require": "./build/webpack-web-app-externals.cjs"
},
"./*": {
"types": [
"./src/*/index.ts",