1
0
mirror of https://github.com/mattermost/focalboard.git synced 2024-11-27 08:31:20 +02:00

Merge pull request #3748 from mattermost/MM-46274_module-federation-poc

MM-46274 Web App Product POC
This commit is contained in:
Harrison Healey 2022-09-13 13:20:11 -04:00 committed by GitHub
commit 096c130edb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 2561 additions and 307 deletions

View File

@ -186,6 +186,10 @@ watch-plugin: modd-precheck ## Run and upload the plugin to a development server
live-watch-plugin: modd-precheck ## Run and update locally the plugin in the development server
cd mattermost-plugin; make live-watch
.PHONY: watch-product
watch-product: ## Run the product as something the Mattermost web app will watch for
cd mattermost-plugin; make watch-product
mac-app: server-mac webapp ## Build Mac application.
rm -rf mac/temp
rm -rf mac/dist

View File

@ -84,7 +84,7 @@ ifneq ($(HAS_SERVER),)
endif
templates-archive: setup-go-work ## Build templates archive file
cd ../server/assets/build-template-archive; go run -tags '$(BUILD_TAGS)' main.go --dir="../templates-boardarchive" --out="../templates.boardarchive"
cd ../server/assets/build-template-archive; go run -tags '$(BUILD_TAGS)' main.go --dir="../templates-boardarchive" --out="../templates.boardarchive"
## Builds the server, if it exists, for all supported architectures.
.PHONY: server
@ -146,7 +146,6 @@ endif
ifneq ($(HAS_WEBAPP),)
mkdir -p dist/$(PLUGIN_ID)/webapp
cp -r webapp/dist dist/$(PLUGIN_ID)/webapp/
cp -r webapp/dist/static/* dist/$(PLUGIN_ID)/pack/static/
endif
cd dist && tar -cvzf $(BUNDLE_NAME) $(PLUGIN_ID)
@ -175,6 +174,10 @@ endif
deploy-from-watch: bundle
./build/bin/pluginctl deploy $(PLUGIN_ID) dist/$(BUNDLE_NAME)
.PHONY: watch-product
watch-product: apply
cd webapp && npm run start:product
## Setup dlv for attaching, identifying the plugin PID for other targets.
.PHONY: setup-attach
setup-attach:

File diff suppressed because it is too large Load Diff

View File

@ -11,7 +11,8 @@
"test": "jest --forceExit --detectOpenHandles --verbose",
"test:watch": "jest --watch",
"test-ci": "jest --forceExit --detectOpenHandles --maxWorkers=2",
"check-types": "tsc"
"check-types": "tsc",
"start:product": "webpack serve --mode=development"
},
"devDependencies": {
"@babel/cli": "7.17.6",
@ -78,18 +79,19 @@
"ts-loader": "9.2.8",
"typescript": "4.6.2",
"webpack": "5.70.0",
"webpack-cli": "4.9.2"
"webpack-cli": "4.10.0",
"webpack-dev-server": "4.10.0"
},
"dependencies": {
"core-js": "3.21.1",
"glob-parent": "6.0.2",
"marked": ">=4.0.12",
"mattermost-redux": "5.33.1",
"react": "^17.0.2",
"react-dom": "17.0.2",
"react-intl": "^5.24.7",
"react-redux": "^7.2.8",
"react-router-dom": "5.2.0",
"react": "^16.13.0",
"react-dom": "^16.13.0",
"react-intl": "^5.20.0",
"react-redux": "^7.2.0",
"react-router-dom": "^5.2.0",
"trim-newlines": "4.0.2"
},
"jest": {

View File

@ -129,7 +129,7 @@ exports[`components/rhsChannelBoards renders with empty list of boards 1`] = `
class="boards-screenshots"
>
<img
src="undefined/public/boards-screenshots.png"
src="test-file-stub"
/>
</div>
<button

View File

@ -5,7 +5,7 @@ exports[`components/rhsChannelBoardsHeader renders the header 1`] = `
<div>
<img
class="boards-rhs-header-logo"
src="undefined/public/app-bar-icon.png"
src="test-file-stub"
/>
<span>
Boards

View File

@ -27,12 +27,12 @@ import Button from '../../../../webapp/src/widgets/buttons/button'
import {WSClient} from '../../../../webapp/src/wsclient'
import boardsScreenshots from '../../../../webapp/static/boards-screenshots.png'
import RHSChannelBoardItem from './rhsChannelBoardItem'
import './rhsChannelBoards.scss'
const boardsScreenshots = (window as any).baseURL + '/public/boards-screenshots.png'
const RHSChannelBoards = () => {
const boards = useAppSelector(getMySortedBoards)
const teamId = useAppSelector(getCurrentTeamId)

View File

@ -7,9 +7,11 @@ import {getMessages} from '../../../../webapp/src/i18n'
import {getLanguage} from '../../../../webapp/src/store/language'
import {getCurrentChannel} from '../../../../webapp/src/store/channels'
import {useAppSelector} from '../../../../webapp/src/store/hooks'
import {Utils} from '../../../../webapp/src/utils'
import appBarIcon from '../../../../webapp/static/app-bar-icon.png'
const RHSChannelBoardsHeader = () => {
const appBarIconURL = (window as any).baseURL + '/public/app-bar-icon.png'
const currentChannel = useAppSelector(getCurrentChannel)
const language = useAppSelector<string>(getLanguage)
@ -25,7 +27,7 @@ const RHSChannelBoardsHeader = () => {
<div>
<img
className='boards-rhs-header-logo'
src={appBarIconURL}
src={Utils.buildURL(appBarIcon, true)}
/>
<span>
<FormattedMessage

View File

@ -16,9 +16,8 @@ import {SuiteWindow} from '../../../webapp/src/types/index'
import {UserSettings} from '../../../webapp/src/userSettings'
import {getMessages, getCurrentLanguage} from '../../../webapp/src/i18n'
const windowAny = (window as SuiteWindow)
windowAny.baseURL = '/plugins/focalboard'
windowAny.baseURL = process.env.TARGET_IS_PRODUCT ? '/plugins/boards' : '/plugins/focalboard'
windowAny.frontendBaseURL = '/boards'
windowAny.isFocalboardPlugin = true
@ -41,6 +40,8 @@ import '../../../webapp/src/styles/labels.scss'
import octoClient from '../../../webapp/src/octoClient'
import {Constants} from '../../../webapp/src/constants'
import appBarIcon from '../../../webapp/static/app-bar-icon.png'
import BoardsUnfurl from './components/boardsUnfurl/boardsUnfurl'
import RHSChannelBoards from './components/rhsChannelBoards'
import RHSChannelBoardsHeader from './components/rhsChannelBoardsHeader'
@ -319,8 +320,7 @@ export default class Plugin {
}
if (this.registry.registerAppBarComponent) {
const appBarIconURL = windowAny.baseURL + '/public/app-bar-icon.png'
this.registry.registerAppBarComponent(appBarIconURL, () => mmStore.dispatch(toggleRHSPlugin), intl.formatMessage({id: 'AppBar.Tooltip', defaultMessage: 'Toggle Linked Boards'}))
this.registry.registerAppBarComponent(Utils.buildURL(appBarIcon, true), () => mmStore.dispatch(toggleRHSPlugin), intl.formatMessage({id: 'AppBar.Tooltip', defaultMessage: 'Toggle Linked Boards'}))
}
this.registry.registerPostWillRenderEmbedComponent(
@ -343,7 +343,7 @@ export default class Plugin {
const data = await octoClient.getMyTopBoards(timeRange, page, perPage, teamId)
return data
}
}
const data = await octoClient.getTeamTopBoards(timeRange, page, perPage, teamId)
@ -417,11 +417,3 @@ export default class Plugin {
this.registry?.unregisterWebSocketEventHandler(wsClient.clientPrefix + ACTION_UPDATE_BLOCK)
}
}
declare global {
interface Window {
registerPlugin(id: string, plugin: Plugin): void
}
}
window.registerPlugin(manifest.id, new Plugin())

View File

@ -0,0 +1,14 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import manifest from './manifest'
import Plugin from './index'
declare global {
interface Window {
registerPlugin(id: string, plugin: Plugin): void
}
}
window.registerPlugin(manifest.id, new Plugin())

View File

@ -0,0 +1,4 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import('./index')

View File

@ -5,16 +5,19 @@ const exec = require('child_process').exec;
const path = require('path');
const webpack = require('webpack');
const {ModuleFederationPlugin} = require('webpack').container;
const tsTransformer = require('@formatjs/ts-transformer');
const PLUGIN_ID = require('../plugin.json').id;
const NPM_TARGET = process.env.npm_lifecycle_event; //eslint-disable-line no-process-env
const TARGET_IS_PRODUCT = NPM_TARGET === 'start:product' || NPM_TARGET === 'build:product';
let mode = 'production';
let devtool;
const plugins = [];
if (NPM_TARGET === 'debug' || NPM_TARGET === 'debug:watch') {
if (NPM_TARGET === 'debug' || NPM_TARGET === 'debug:watch' || NPM_TARGET === 'start:product') {
mode = 'development';
devtool = 'source-map';
plugins.push(
@ -49,10 +52,8 @@ if (NPM_TARGET === 'build:watch' || NPM_TARGET === 'debug:watch' || NPM_TARGET =
});
}
module.exports = {
entry: [
'./src/index.tsx',
],
const config = {
entry: TARGET_IS_PRODUCT ? './src/remote_entry.ts' : './src/plugin_entry.ts',
resolve: {
modules: [
'src',
@ -61,7 +62,6 @@ module.exports = {
],
alias: {
moment: path.resolve(__dirname, '../../webapp/node_modules/moment/'),
'react-intl': path.resolve(__dirname, '../../webapp/node_modules/react-intl/'),
},
extensions: ['*', '.js', '.jsx', '.ts', '.tsx'],
},
@ -115,35 +115,99 @@ module.exports = {
test: /\.(png|eot|tiff|svg|woff2|woff|ttf|jpg|gif)$/,
type: 'asset/resource',
generator: {
filename: 'static/[name].[ext]',
filename: '[name][ext]',
publicPath: TARGET_IS_PRODUCT ? 'http://localhost:9006/static/' : '/static/',
}
},
],
},
externals: {
react: 'React',
redux: 'Redux',
'react-redux': 'ReactRedux',
'mm-react-router-dom': 'ReactRouterDom',
'prop-types': 'PropTypes',
'react-bootstrap': 'ReactBootstrap',
},
output: {
devtoolNamespace: PLUGIN_ID,
path: path.join(__dirname, '/dist'),
publicPath: '/',
filename: 'main.js',
},
devtool,
mode,
plugins,
};
if (TARGET_IS_PRODUCT) {
// Set up module federation
function makeSingletonSharedModules(packageNames) {
const sharedObject = {};
for (const packageName of packageNames) {
// Set both versions to false so that the version of this module provided by the web app will be used
sharedObject[packageName] = {
requiredVersion: false,
singleton: true,
version: false,
};
}
return sharedObject;
}
config.plugins.push(new ModuleFederationPlugin({
name: PLUGIN_ID,
filename: 'remote_entry.js',
exposes: {
'.': './src/index',
// This probably won't need to be exposed in the long run, but its a POC for exposing multiple modules
'./manifest': './src/manifest',
},
shared: [
'@mattermost/client',
'prop-types',
makeSingletonSharedModules([
'react',
'react-dom',
'react-intl',
'react-redux',
'react-router-dom',
]),
],
}));
config.plugins.push(new webpack.DefinePlugin({
'process.env.TARGET_IS_PRODUCT': TARGET_IS_PRODUCT, // TODO We might want a better name for this
}));
} else {
config.resolve.alias['react-intl'] = path.resolve(__dirname, '../../webapp/node_modules/react-intl/');
config.externals = {
react: 'React',
redux: 'Redux',
'react-redux': 'ReactRedux',
'mm-react-router-dom': 'ReactRouterDom',
'prop-types': 'PropTypes',
'react-bootstrap': 'ReactBootstrap',
};
config.output = {
devtoolNamespace: PLUGIN_ID,
path: path.join(__dirname, '/dist'),
publicPath: '/',
filename: 'main.js',
};
}
const env = {};
env.RUDDER_KEY = JSON.stringify(process.env.RUDDER_KEY || ''); //eslint-disable-line no-process-env
env.RUDDER_DATAPLANE_URL = JSON.stringify(process.env.RUDDER_DATAPLANE_URL || ''); //eslint-disable-line no-process-env
module.exports.plugins.push(new webpack.DefinePlugin({
config.plugins.push(new webpack.DefinePlugin({
'process.env': env,
}));
if (NPM_TARGET === 'start:product') {
config.devServer = {
port: 9006,
devMiddleware: {
writeToDisk: false,
},
static: {
directory: path.join(__dirname, '../../webapp/static'),
publicPath: '/static',
},
};
}
module.exports = config;

View File

@ -34,18 +34,18 @@
"mini-create-react-context": "^0.4.1",
"moment": "^2.29.1",
"nanoevents": "^5.1.13",
"react": "^17.0.2",
"react": "^16.13.0",
"react-day-picker": "^7.4.10",
"react-dnd": "^14.0.2",
"react-dnd-html5-backend": "^14.0.0",
"react-dnd-scrolling": "^1.2.1",
"react-dnd-touch-backend": "^14.0.0",
"react-dom": "^17.0.2",
"react-dom": "^16.13.0",
"react-hot-keys": "^2.7.1",
"react-hotkeys-hook": "^3.4.4",
"react-intl": "^5.24.7",
"react-redux": "^7.2.6",
"react-router-dom": "^5.2.1",
"react-intl": "^5.20.0",
"react-redux": "^7.2.0",
"react-router-dom": "^5.2.0",
"react-select": "^5.2.2",
"trim-newlines": "^4.0.2"
},
@ -12782,12 +12782,13 @@
}
},
"node_modules/react": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz",
"integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==",
"version": "16.14.0",
"resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz",
"integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==",
"dependencies": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1"
"object-assign": "^4.1.1",
"prop-types": "^15.6.2"
},
"engines": {
"node": ">=0.10.0"
@ -12887,16 +12888,17 @@
}
},
"node_modules/react-dom": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz",
"integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==",
"version": "16.14.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz",
"integrity": "sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==",
"dependencies": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1",
"scheduler": "^0.20.2"
"prop-types": "^15.6.2",
"scheduler": "^0.19.1"
},
"peerDependencies": {
"react": "17.0.2"
"react": "^16.14.0"
}
},
"node_modules/react-fast-compare": {
@ -13671,9 +13673,9 @@
}
},
"node_modules/scheduler": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz",
"integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==",
"version": "0.19.1",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz",
"integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==",
"dependencies": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1"
@ -25657,12 +25659,13 @@
}
},
"react": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz",
"integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==",
"version": "16.14.0",
"resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz",
"integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==",
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1"
"object-assign": "^4.1.1",
"prop-types": "^15.6.2"
}
},
"react-custom-scrollbars-2": {
@ -25730,13 +25733,14 @@
}
},
"react-dom": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz",
"integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==",
"version": "16.14.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz",
"integrity": "sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==",
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1",
"scheduler": "^0.20.2"
"prop-types": "^15.6.2",
"scheduler": "^0.19.1"
}
},
"react-fast-compare": {
@ -26307,9 +26311,9 @@
}
},
"scheduler": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz",
"integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==",
"version": "0.19.1",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz",
"integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==",
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1"

View File

@ -50,18 +50,18 @@
"mini-create-react-context": "^0.4.1",
"moment": "^2.29.1",
"nanoevents": "^5.1.13",
"react": "^17.0.2",
"react": "^16.13.0",
"react-day-picker": "^7.4.10",
"react-dnd": "^14.0.2",
"react-dnd-html5-backend": "^14.0.0",
"react-dnd-scrolling": "^1.2.1",
"react-dnd-touch-backend": "^14.0.0",
"react-dom": "^17.0.2",
"react-dom": "^16.13.0",
"react-hot-keys": "^2.7.1",
"react-hotkeys-hook": "^3.4.4",
"react-intl": "^5.24.7",
"react-redux": "^7.2.6",
"react-router-dom": "^5.2.1",
"react-intl": "^5.20.0",
"react-redux": "^7.2.0",
"react-router-dom": "^5.2.0",
"react-select": "^5.2.2",
"trim-newlines": "^4.0.2"
},

View File

@ -88,14 +88,14 @@ class Utils {
}
static getUserDisplayName(user: IUser, configNameFormat: string): string {
let nameFormat = configNameFormat
let nameFormat = configNameFormat
if(UserSettings.nameFormat){
nameFormat=UserSettings.nameFormat
}
// default nameFormat = 'username'
let displayName = user.username
if (nameFormat === ShowNicknameFullName) {
if( user.nickname != '') {
displayName = user.nickname
@ -574,7 +574,7 @@ class Utils {
}
static buildURL(path: string, absolute?: boolean): string {
if (!Utils.isFocalboardPlugin()) {
if (!Utils.isFocalboardPlugin() || process.env.TARGET_IS_PRODUCT) {
return path
}

View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

Before

Width:  |  Height:  |  Size: 196 KiB

After

Width:  |  Height:  |  Size: 196 KiB