agora-toolchain 1.0.1-1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/env/electron.setup.ts +44 -0
  2. package/env/karma-setup.ts +5 -0
  3. package/package.json +77 -0
  4. package/presets/.babelrc +25 -0
  5. package/presets/karma.conf.js +88 -0
  6. package/presets/postcss.config.js +5 -0
  7. package/presets/tsconfig.base.json +31 -0
  8. package/presets/webpack.base.js +76 -0
  9. package/presets/webpack.dev.js +186 -0
  10. package/presets/webpack.karma.js +29 -0
  11. package/presets/webpack.prod.js +157 -0
  12. package/public/electron-native-libs/mac/FcrUIScene.framework/Versions/A/FcrUIScene +0 -0
  13. package/public/electron-native-libs/mac/FcrUIScene.framework/Versions/A/Headers/FcrEnums.h +40 -0
  14. package/public/electron-native-libs/mac/FcrUIScene.framework/Versions/A/Headers/FcrError.h +13 -0
  15. package/public/electron-native-libs/mac/FcrUIScene.framework/Versions/A/Headers/FcrObjects.h +76 -0
  16. package/public/electron-native-libs/mac/FcrUIScene.framework/Versions/A/Headers/FcrUIPlatform.h +87 -0
  17. package/public/electron-native-libs/mac/FcrUIScene.framework/Versions/A/Headers/IFcrUISceneCreator.h +29 -0
  18. package/public/electron-native-libs/mac/FcrUIScene.framework/Versions/A/Resources/Info.plist +48 -0
  19. package/public/electron-native-libs/mac/FcrUISceneLauncher.node +0 -0
  20. package/public/electron-native-libs/win/FcrUIScene.dll +0 -0
  21. package/public/electron-native-libs/win/FcrUISceneLauncher.node +0 -0
  22. package/public/index.html +11 -0
  23. package/scripts/bundle.js +15 -0
  24. package/scripts/dev-server.js +74 -0
  25. package/scripts/pack-client-app.js +156 -0
  26. package/scripts/pack-client-sdk.js +162 -0
  27. package/scripts/sign-client.js +5 -0
  28. package/scripts/test-karma-browser.js +19 -0
  29. package/scripts/test-karma-electron.js +53 -0
  30. package/scripts/transpile.js +64 -0
  31. package/tools/ast-utils.js +68 -0
  32. package/tools/bundle-options.js +10 -0
  33. package/tools/dev-server-options.js +12 -0
  34. package/tools/notarize.js +1 -0
  35. package/tools/pack-options.js +11 -0
  36. package/tools/paths.js +36 -0
  37. package/tools/process-utils.js +15 -0
  38. package/tools/transpile-options.js +10 -0
  39. package/tools/webpack-utils.js +21 -0
  40. package/webpack-loaders/fragment-loader.js +27 -0
  41. package/webpack-loaders/ui-module-loader.js +9 -0
  42. package/webpack-plugins/append-code-plugin.js +40 -0
@@ -0,0 +1,44 @@
1
+ import { app, BrowserWindow } from 'electron';
2
+ import { initialize, enable } from '@electron/remote/main';
3
+
4
+ let baseUrl = process.env.ELECTRON_START_URL ?? `http://localhost:3000`;
5
+
6
+ const defaultWindowOptions: Electron.BrowserWindowConstructorOptions = {
7
+ height: 850,
8
+ width: 1340,
9
+ minWidth: 825, // 这个值会影响主窗口的最小宽度,如需改变,记得同步 /fcr-ui-scene-desktop/src/modules/state-bar/store.ts 中的 GALLERY_MIN_SIZE
10
+ minHeight: 600,
11
+ };
12
+
13
+ const defaultWebPreferences: Electron.WebPreferences = {
14
+ nodeIntegration: true,
15
+ nodeIntegrationInWorker: true,
16
+ contextIsolation: false,
17
+ webSecurity: false,
18
+ webviewTag: true,
19
+ backgroundThrottling: false,
20
+ };
21
+
22
+ initialize();
23
+
24
+ app.addListener('ready', () => {
25
+ const mainWindow = new BrowserWindow({
26
+ ...defaultWindowOptions,
27
+ webPreferences: {
28
+ ...defaultWebPreferences,
29
+ },
30
+ show: false,
31
+ });
32
+
33
+ mainWindow.addListener('closed', () => {
34
+ app.exit(0);
35
+ });
36
+
37
+ mainWindow.addListener('ready-to-show', () => {
38
+ mainWindow.show();
39
+ });
40
+
41
+ enable(mainWindow.webContents);
42
+
43
+ mainWindow.loadURL(baseUrl);
44
+ });
@@ -0,0 +1,5 @@
1
+ import { Buffer } from "buffer/";
2
+ // @ts-ignore
3
+ window.Buffer = Buffer;
4
+ // @ts-ignore
5
+ window.require = parent.window.require;
package/package.json ADDED
@@ -0,0 +1,77 @@
1
+ {
2
+ "name": "agora-toolchain",
3
+ "version": "1.0.1-1",
4
+ "main": "index.js",
5
+ "license": "MIT",
6
+ "bin": {
7
+ "agora-tc-transpile": "./scripts/transpile.js",
8
+ "agora-tc-test-karma-browser": "./scripts/test-karma-browser.js",
9
+ "agora-tc-test-karma-electron": "./scripts/test-karma-electron.js",
10
+ "agora-tc-dev-server": "./scripts/dev-server.js",
11
+ "agora-tc-bundle": "./scripts/bundle.js",
12
+ "agora-tc-pack-app": "./scripts/pack-client-app.js",
13
+ "agora-tc-pack-sdk": "./scripts/pack-client-sdk.js",
14
+ "agora-tc-sign": "./scripts/sign-client.js"
15
+ },
16
+ "devDependencies": {
17
+ "@types/node": "^20.10.3",
18
+ "@types/react": "^17.0.2"
19
+ },
20
+ "peerDependencies": {
21
+ "tslib": "^2.6.2"
22
+ },
23
+ "browserslist": {
24
+ "production": [
25
+ ">0.3%",
26
+ "not dead",
27
+ "not op_mini all"
28
+ ],
29
+ "development": [
30
+ ">0.3%",
31
+ "last 1 chrome version",
32
+ "last 1 firefox version",
33
+ "last 1 safari version"
34
+ ]
35
+ },
36
+ "dependencies": {
37
+ "commander": "^12.0.0",
38
+ "dotenv": "^16.3.1",
39
+ "@babel/cli": "^7.17.6",
40
+ "@babel/core": "^7.23.5",
41
+ "@babel/plugin-proposal-decorators": "^7.23.5",
42
+ "@babel/plugin-transform-runtime": "^7.13.15",
43
+ "@babel/preset-env": "^7.23.5",
44
+ "@babel/preset-react": "^7.24.1",
45
+ "@babel/preset-typescript": "^7.23.3",
46
+ "@hot-loader/react-dom": "^17.0.2",
47
+ "autoprefixer": "^10.4.19",
48
+ "babel-loader": "^9.1.3",
49
+ "buffer": "^6.0.3",
50
+ "core-js": "^3.33.3",
51
+ "crypto-browserify": "^3.12.0",
52
+ "css-loader": "^6.10.0",
53
+ "html-webpack-plugin": "^5.6.0",
54
+ "webpack-bundle-analyzer": "^4.10.2",
55
+ "karma": "^6.4.2",
56
+ "karma-chrome-launcher": "^3.2.0",
57
+ "karma-jasmine": "^5.1.0",
58
+ "karma-webpack": "^5.0.0",
59
+ "postcss-loader": "^8.1.1",
60
+ "process": "^0.11.10",
61
+ "react-hot-loader": "^4.13.1",
62
+ "stream-browserify": "^3.0.0",
63
+ "style-loader": "^3.3.4",
64
+ "ts-loader": "^9.5.1",
65
+ "ts-node": "^10.9.1",
66
+ "typescript": "^5.4.3",
67
+ "vm-browserify": "^1.1.2",
68
+ "wait-on": "^7.2.0",
69
+ "webpack": "^5.10.0",
70
+ "webpack-dev-server": "^5.0.4",
71
+ "webpack-merge": "^5.10.0",
72
+ "worker-loader": "^3.0.8",
73
+ "zlib-browserify": "^0.0.3",
74
+ "mini-css-extract-plugin": "^2.9.1",
75
+ "electron-builder": "^24.13.3"
76
+ }
77
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "presets": [
3
+ [
4
+ "@babel/preset-env",
5
+ {
6
+ "useBuiltIns": "usage",
7
+ "corejs": {
8
+ "version": "3.33",
9
+ "proposals": false
10
+ }
11
+ }
12
+ ],
13
+ [
14
+ "@babel/preset-react",
15
+ {
16
+ "runtime": "automatic"
17
+ }
18
+ ],
19
+ "@babel/preset-typescript"
20
+ ],
21
+ "plugins": [
22
+ ["@babel/plugin-proposal-decorators", { "version": "2023-05" }],
23
+ ["@babel/plugin-transform-runtime", { "regenerator": true }]
24
+ ]
25
+ }
@@ -0,0 +1,88 @@
1
+ require('dotenv/config');
2
+ const webpackConfig = require('./webpack.karma')();
3
+ const path = require('path');
4
+ const cwd = process.cwd();
5
+
6
+ const files =
7
+ process.env.platform === 'browser'
8
+ ? ['__test__/browser/**/*.test.ts']
9
+ : ['__test__/electron/**/*.test.ts'];
10
+
11
+ const preprocessors =
12
+ process.env.platform === 'browser'
13
+ ? {
14
+ [path.join(cwd, '__test__/browser/**/*.test.ts')]: ['webpack'],
15
+ }
16
+ : {
17
+ [path.join(cwd, '__test__/electron/**/*.test.ts')]: ['webpack'],
18
+ };
19
+
20
+ const browsers = process.env.platform === 'browser' ? ['Chrome'] : [];
21
+
22
+ module.exports = (config) => {
23
+ config.set({
24
+ // base path that will be used to resolve all patterns (eg. files, exclude)
25
+ basePath: cwd,
26
+ // frameworks to use
27
+ // available frameworks: https://www.npmjs.com/search?q=keywords:karma-adapter
28
+ frameworks: ['jasmine', 'webpack'],
29
+ webpack: webpackConfig,
30
+ // list of files / patterns to load in the browser
31
+ files: [
32
+ path.join(path.resolve(__dirname, '..'), 'env/karma-setup.ts'),
33
+ '__test__/utilities/*.ts',
34
+ ...files,
35
+ {
36
+ pattern: '__test__/fixtures/**/*',
37
+ watched: false,
38
+ included: false,
39
+ served: true,
40
+ nocache: false,
41
+ },
42
+ ],
43
+ proxies: {
44
+ '/fixtures/': `/base/__test__/fixtures/`,
45
+ },
46
+
47
+ // list of files / patterns to exclude
48
+ exclude: [],
49
+
50
+ // preprocess matching files before serving them to the browser
51
+ // available preprocessors: https://www.npmjs.com/search?q=keywords:karma-preprocessor
52
+ preprocessors: {
53
+ [path.join(path.resolve(__dirname, '..'), 'env/karma-setup.ts')]: ['webpack'],
54
+ [path.join(cwd, '__test__/utilities/*.ts')]: ['webpack'],
55
+ ...preprocessors,
56
+ },
57
+
58
+ // test results reporter to use
59
+ // possible values: 'dots', 'progress'
60
+ // available reporters: https://www.npmjs.com/search?q=keywords:karma-reporter
61
+ reporters: ['progress'],
62
+
63
+ // web server port
64
+ port: 9876,
65
+
66
+ // enable / disable colors in the output (reporters and logs)
67
+ colors: true,
68
+
69
+ // level of logging
70
+ // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
71
+ logLevel: config.LOG_INFO,
72
+
73
+ // enable / disable watching file and executing tests whenever any file changes
74
+ autoWatch: true,
75
+
76
+ // start these browsers
77
+ // available browser launchers: https://www.npmjs.com/search?q=keywords:karma-launcher
78
+ browsers,
79
+
80
+ // Continuous Integration mode
81
+ // if true, Karma captures browsers, runs the tests and exits
82
+ singleRun: false,
83
+
84
+ // Concurrency level
85
+ // how many browser instances should be started simultaneously
86
+ concurrency: Infinity,
87
+ });
88
+ };
@@ -0,0 +1,5 @@
1
+ const autoprefixer = require("autoprefixer");
2
+
3
+ module.exports = {
4
+ plugins: [autoprefixer()],
5
+ };
@@ -0,0 +1,31 @@
1
+ {
2
+ "compilerOptions": {
3
+ "jsx": "react-jsx",
4
+ "target": "es6",
5
+ "module": "esnext",
6
+ "lib": ["dom", "esnext", "DOM.Iterable", "WebWorker"],
7
+ "moduleResolution": "node",
8
+ "importHelpers": true,
9
+ "declaration": true,
10
+ // "pretty": true,
11
+ // "sourceMap": true,
12
+ // "inlineSources": true,
13
+ "esModuleInterop": true,
14
+ "strict": true,
15
+ "skipLibCheck": true,
16
+ "noEmitHelpers": true,
17
+ // "noEmitOnError": false,
18
+ "emitDeclarationOnly": true,
19
+ "isolatedModules": true,
20
+ // no need experimentalDecorators anymore after typescript 5.0
21
+ // "experimentalDecorators": true,
22
+ "allowJs": false,
23
+ "stripInternal": true,
24
+ "resolveJsonModule": true
25
+ },
26
+ "ts-node": {
27
+ "compilerOptions": {
28
+ "module": "commonjs"
29
+ }
30
+ }
31
+ }
@@ -0,0 +1,76 @@
1
+ const { DefinePlugin, ProvidePlugin } = require('webpack');
2
+ const path = require('path');
3
+ const { sourceAlias } = require('../tools/webpack-utils');
4
+ const { resolveCwd, resolveModule } = require('../tools/paths');
5
+ const cwd = process.cwd();
6
+
7
+ module.exports = {
8
+ plugins: [
9
+ new ProvidePlugin({
10
+ process: 'process/browser',
11
+ }),
12
+ new DefinePlugin({
13
+ AGORA_TEST_RTM_APP_ID: JSON.stringify(process.env.AGORA_TEST_RTM_APP_ID),
14
+ AGORA_TEST_RTM_APP_CERTIFICATE: JSON.stringify(process.env.AGORA_TEST_RTM_APP_CERTIFICATE),
15
+ DEMO_APP_VERSION: JSON.stringify(require(resolveCwd('package.json')).version),
16
+ DEMO_APP_SHARE_LINK: JSON.stringify(process.env.fcr_app_share_link),
17
+ }),
18
+ ],
19
+ resolve: {
20
+ fallback: {
21
+ crypto: require.resolve('crypto-browserify'),
22
+ stream: require.resolve('stream-browserify'),
23
+ zlib: require.resolve('zlib-browserify'),
24
+ vm: require.resolve('vm-browserify'),
25
+ },
26
+ alias: {
27
+ '@src': resolveCwd('src'),
28
+ 'process/browser': resolveModule('process'),
29
+ react: resolveModule('react', true),
30
+ mobx: resolveModule('mobx', true),
31
+ ...sourceAlias(cwd, [
32
+ 'agora-foundation',
33
+ 'agora-ui-foundation',
34
+ 'agora-rte-sdk',
35
+ 'agora-edu-core',
36
+ 'fcr-ui-scene',
37
+ ]),
38
+ },
39
+ extensions: ['.ts', '.js', '.tsx'],
40
+ },
41
+ externals: {
42
+ 'agora-electron-sdk': 'commonjs2 agora-electron-sdk',
43
+ electron: 'commonjs2 electron',
44
+ },
45
+ module: {
46
+ rules: [
47
+ {
48
+ test: /worker-entry/,
49
+ use: {
50
+ loader: require.resolve('worker-loader'),
51
+ options: {
52
+ inline: 'no-fallback',
53
+ },
54
+ },
55
+ },
56
+ {
57
+ test: /\.(ts|tsx)$/,
58
+ use: [
59
+ {
60
+ loader: require.resolve('ts-loader'),
61
+ options: {
62
+ transpileOnly: true,
63
+ },
64
+ },
65
+ // path.resolve(__dirname, '..', 'webpack-loaders/fragment-loader'),
66
+ // path.resolve(__dirname, '..', 'webpack-loaders/ui-module-loader'),
67
+ ],
68
+ exclude: /node_modules/,
69
+ },
70
+ {
71
+ test: /\.(png|jpe?g|gif|svg|mp4|webm|ogg|mp3|wav|flac|aac|woff|woff2|eot|ttf)$/,
72
+ type: 'asset',
73
+ },
74
+ ],
75
+ },
76
+ };
@@ -0,0 +1,186 @@
1
+ const webpackMerge = require('webpack-merge');
2
+ const HtmlWebpackPlugin = require('html-webpack-plugin');
3
+ const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
4
+ const path = require('path');
5
+ // const MiniCssExtractPlugin = require('mini-css-extract-plugin');
6
+ const { AppendCodePlugin } = require('../webpack-plugins/append-code-plugin');
7
+ const { getCodeOfDeclaredVariables } = require('../tools/ast-utils');
8
+ const baseConfig = require('./webpack.base');
9
+ const { resolveBase, resolveCwd, resolveScene } = require('../tools/paths');
10
+ const fs = require('fs');
11
+
12
+ const isSourceAttached = () => {
13
+ const srcPath = resolveScene(`src`);
14
+
15
+ if (fs.existsSync(srcPath)) {
16
+ return true;
17
+ }
18
+ return false;
19
+ };
20
+
21
+ module.exports = createConfig = ({ entry, port = 3000, analyze }) => {
22
+ const extractArray = (str = '') => {
23
+ if (!str) {
24
+ return [];
25
+ }
26
+
27
+ return str.split(',').map((s) => s.trim());
28
+ };
29
+
30
+ const plugins = extractArray(process.env.fcr_plugins);
31
+ const fragments = extractArray(process.env.fcr_fragments);
32
+ const fragmentPlugins = extractArray(process.env.fcr_fragment_plugins);
33
+ const modules = extractArray(process.env.fcr_dev_module);
34
+
35
+ const htmlPlugins = [
36
+ new HtmlWebpackPlugin({
37
+ filename: 'index.html',
38
+ template: resolveCwd('public/index.html'),
39
+ inject: true,
40
+ chunks: ['main'],
41
+ title: process.env.fcr_app_name ?? 'Development HTML',
42
+ }),
43
+ ];
44
+
45
+ const codeAppendPlugins = [];
46
+
47
+ const otherPlugins = [];
48
+
49
+ if (modules.length) {
50
+ console.log('dev module:', modules);
51
+ }
52
+ if (plugins.length) {
53
+ console.log('plugins:', plugins);
54
+ }
55
+ if (fragments.length) {
56
+ console.log('fragments:', fragments);
57
+ }
58
+ if (fragmentPlugins.length) {
59
+ console.log('fragment plugins:', fragmentPlugins);
60
+ }
61
+
62
+ const entries = {
63
+ main: [resolveBase('react-hot-loader/patch', true)],
64
+ };
65
+
66
+ if (modules.length) {
67
+ entries.main.push(resolveScene(`src/modules/${modules[0]}/index.dev`));
68
+ }
69
+
70
+ if (fragments.length) {
71
+ fragments.forEach((fragment) => {
72
+ const fragmentEntry = [resolveBase('react-hot-loader/patch', true)];
73
+
74
+ if (fragmentPlugins.length) {
75
+ fragmentPlugins.forEach((pluginName) => {
76
+ fragmentEntry.push(
77
+ isSourceAttached()
78
+ ? resolveScene(`src/plugins/${pluginName}`)
79
+ : resolveScene(`lib/plugins/${pluginName}`),
80
+ );
81
+ });
82
+ }
83
+
84
+ fragmentEntry.push(
85
+ isSourceAttached()
86
+ ? resolveScene(`src/fragments/${fragment}/index`)
87
+ : resolveScene(`lib/fragments/${fragment}/index`),
88
+ );
89
+
90
+ entries[fragment] = fragmentEntry;
91
+
92
+ const { windowOptions, webPreferences, title } = getCodeOfDeclaredVariables(
93
+ isSourceAttached()
94
+ ? resolveScene(`src/fragments/${fragment}/index.tsx`)
95
+ : resolveScene(`lib/fragments/${fragment}/index.js`),
96
+ ['windowOptions', 'webPreferences', 'title'],
97
+ );
98
+
99
+ codeAppendPlugins.push(
100
+ new AppendCodePlugin({
101
+ code: `window.runtime && window.runtime.setFragmentOptions('${fragment}', ${windowOptions} || {}, ${webPreferences} || {});`,
102
+ patterns: [/main.js$/, ...fragments.map((f) => new RegExp(`${f}.js$`))],
103
+ }),
104
+ );
105
+
106
+ htmlPlugins.push(
107
+ new HtmlWebpackPlugin({
108
+ filename: `${fragment}.html`,
109
+ template: resolveCwd('public/index.html'),
110
+ inject: true,
111
+ chunks: [fragment],
112
+ title: eval(`(${title} ?? '')`),
113
+ }),
114
+ );
115
+ });
116
+ }
117
+
118
+ if (process.env.DEV === 'true') {
119
+ const normalizedNodeModulePath = JSON.stringify(
120
+ path.join(`${process.cwd()}`.replace('\\', '\\\\'), 'node_modules'),
121
+ );
122
+
123
+ // add cwd to node path, so that we can require agora-electron-sdk from cwd
124
+ codeAppendPlugins.push(
125
+ new AppendCodePlugin({
126
+ code: `
127
+ if(window.module && Array.isArray(window.module.paths) && !window.module.paths.includes(${normalizedNodeModulePath})) {
128
+ window.module.paths.unshift(${normalizedNodeModulePath});
129
+ }
130
+ `,
131
+ patterns: [/main.js$/, ...fragments.map((f) => new RegExp(`${f}.js$`))],
132
+ }),
133
+ );
134
+ }
135
+
136
+ if (entry) {
137
+ entries.main.push(resolveCwd(entry));
138
+ }
139
+
140
+ if (analyze) {
141
+ otherPlugins.push(new BundleAnalyzerPlugin());
142
+ }
143
+
144
+ const devConfig = {
145
+ mode: 'development',
146
+ devtool: 'eval-source-map',
147
+ entry: entries,
148
+ devServer: {
149
+ static: [
150
+ {
151
+ directory: resolveCwd('public'),
152
+ publicPath: '/',
153
+ },
154
+ ],
155
+ compress: true,
156
+ port: port,
157
+ },
158
+ module: {
159
+ rules: [
160
+ {
161
+ test: /\.css$/i,
162
+ use: [
163
+ require.resolve('style-loader'),
164
+ // require.resolve(MiniCssExtractPlugin.loader),
165
+ require.resolve('css-loader'),
166
+ require.resolve('postcss-loader'),
167
+ ],
168
+ },
169
+ ],
170
+ },
171
+ plugins: [
172
+ // new MiniCssExtractPlugin({
173
+ // filename: '[name].[contenthash].css',
174
+ // }),
175
+ ...htmlPlugins,
176
+ ...codeAppendPlugins,
177
+ ...otherPlugins,
178
+ ],
179
+ resolve: {
180
+ alias: {
181
+ 'react-dom': resolveBase('@hot-loader/react-dom', true),
182
+ },
183
+ },
184
+ };
185
+ return webpackMerge.merge(baseConfig, devConfig);
186
+ };
@@ -0,0 +1,29 @@
1
+ const webpackMerge = require('webpack-merge');
2
+ const path = require('path');
3
+ const { AppendCodePlugin } = require('../webpack-plugins/append-code-plugin');
4
+ const baseConfig = require('./webpack.base');
5
+
6
+ module.exports = createConfig = () => {
7
+ // add cwd to node path, so that we can require agora-electron-sdk from cwd
8
+
9
+ const normalizedNodeModulePath = JSON.stringify(
10
+ path.join(`${process.cwd()}`.replace('\\', '\\\\'), 'node_modules'),
11
+ );
12
+
13
+ const codeAppendPlugins = [
14
+ new AppendCodePlugin({
15
+ code: `
16
+ if(parent.module && Array.isArray(parent.module.paths) && !parent.module.paths.includes(${normalizedNodeModulePath})) {
17
+ parent.module.paths.unshift(${normalizedNodeModulePath});
18
+ }
19
+ `,
20
+ patterns: [/\.test\..*\.js$/],
21
+ }),
22
+ ];
23
+
24
+ const karmaWebpackConfig = {
25
+ plugins: [...codeAppendPlugins],
26
+ };
27
+
28
+ return webpackMerge.merge(baseConfig, karmaWebpackConfig);
29
+ };