@elliemae/pui-cli 6.0.0-beta.2 → 6.0.0-beta.20

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.
@@ -10,7 +10,7 @@ const nodeEnvPreset = {
10
10
  const webEnvPreset = {
11
11
  modules: process.env.ES_MODULES === 'false' ? 'commonjs' : false,
12
12
  useBuiltIns: 'usage',
13
- corejs: { version: '3.18', proposals: true },
13
+ corejs: { version: '3.19', proposals: true },
14
14
  };
15
15
 
16
16
  const presetEnvOptions =
@@ -39,13 +39,14 @@ const config = {
39
39
  '@babel/plugin-proposal-class-properties',
40
40
  '@babel/plugin-syntax-dynamic-import',
41
41
  '@babel/plugin-proposal-export-default-from',
42
+ 'lodash',
43
+ 'date-fns',
42
44
  ],
43
45
  env: {
44
46
  development: {
45
47
  plugins: [
46
48
  ['babel-plugin-styled-components', { displayName: true }],
47
49
  '@babel/plugin-transform-react-jsx-source',
48
- 'react-refresh/babel',
49
50
  ],
50
51
  },
51
52
  production: {
@@ -79,7 +80,11 @@ if (process.env.MODULE_EXTENSIONS === 'true') {
79
80
  config.plugins.push('babel-plugin-add-import-extension');
80
81
  }
81
82
 
82
- // ToDo: Once ECC team migrates from webpack 3 to webpack 5 remove tis strip-block plugin from commonjs output. without this they are receiving error when import.meta is used in app sdk
83
+ if (process.env.STORYBOOK_BUILD !== 'true') {
84
+ config.env?.development?.plugins?.push?.('react-refresh/babel');
85
+ }
86
+
87
+ // ToDo: Once ECC team migrates from webpack 3 to webpack 5 remove this strip-block plugin from commonjs output. without this they are receiving error when import.meta is used in app sdk
83
88
  if (process.env.ES_MODULES === 'false') {
84
89
  config.plugins.push([
85
90
  'babel-plugin-transform-strip-block',
@@ -21,7 +21,7 @@ async function buildWebApp() {
21
21
  async function buildService() {
22
22
  await exec('rimraf ./build');
23
23
  await exec(
24
- `cross-env NODE_ENV=production MODULE_EXTENSIONS=true TARGET_ENV=node babel --extensions '.ts,.js' --config-file ./babel.config.cjs --out-dir ./build --ignore '**/*.test.js','**/*.test.ts' ./app`,
24
+ `cross-env NODE_ENV=production MODULE_EXTENSIONS=true TARGET_ENV=node babel --extensions '.ts,.js' --config-file ./babel.config.cjs --out-dir ./build --copy-files --no-copy-ignored --ignore '**/*.test.ts','**/*.test.js','**/*.spec.ts','**/*.test.js' ./app`,
25
25
  );
26
26
  }
27
27
 
@@ -1,10 +1,11 @@
1
1
  const { exit } = require('yargs');
2
2
  const { exec, logError, logSuccess } = require('./utils');
3
- const { isTypeScripEnabled } = require('../typescript/util');
3
+ const { isTypeScriptEnabled } = require('../typescript/util');
4
4
 
5
- async function lintCSS() {
5
+ async function lintCSS(fix = false) {
6
+ const fixIssues = fix ? '--fix' : '';
6
7
  await exec(
7
- `stylelint "./**/*.{js, ts}" --color --allowEmptyInput --ignore-pattern '/dist/**/*'`,
8
+ `stylelint ./{lib,app}/**/*.{js,jsx,ts,tsx} ${fixIssues} --color --allowEmptyInput --ignore-pattern /dist/**/*`,
8
9
  );
9
10
  }
10
11
 
@@ -18,7 +19,8 @@ async function lintJS(fix = false) {
18
19
  async function handler(argv) {
19
20
  if (argv.js) {
20
21
  // run typescript compilation
21
- if (isTypeScripEnabled) await exec('tsc');
22
+ if (isTypeScriptEnabled())
23
+ await exec('tsc --noEmit --emitDeclarationOnly false');
22
24
  try {
23
25
  await exec('rimraf ./reports/eslint.json');
24
26
  await lintJS(argv.fix);
@@ -1,8 +1,10 @@
1
+ /* eslint-disable max-lines */
1
2
  const { exit } = require('yargs');
2
3
  const path = require('path');
3
4
  const { writeFile, readFile } = require('fs/promises');
4
5
  const { exec, logInfo, logError, logSuccess } = require('./utils');
5
- const { isTypeScripEnabled } = require('../typescript/util');
6
+ const { isTypeScriptEnabled } = require('../typescript/util');
7
+ const { esBuild } = require('../esbuild');
6
8
 
7
9
  const { name } = require('../../package.json');
8
10
 
@@ -37,14 +39,9 @@ async function createPackageJson(file, commonJS, sideEffects) {
37
39
  await writeFile(file, packageJSON);
38
40
  }
39
41
 
40
- async function nodeBuild({ srcPath, commonJS, emitModuleType, target }) {
42
+ async function nodeBuild({ srcPath, commonJS, emitModuleType }) {
41
43
  const outDir = `./dist/${commonJS ? 'cjs' : 'es'}`;
42
- const targetEnv = target === 'node' ? 'TARGET_ENV=node' : '';
43
- const babelEnv = commonJS ? ' ES_MODULES=false' : '';
44
- await exec(
45
- `cross-env NODE_ENV=production MODULE_EXTENSIONS=true ${targetEnv}${babelEnv} babel --extensions ".ts,.tsx,.js,.jsx" ${srcPath} --out-dir ${outDir} --copy-files --ignore **/*.test.js --ignore **/*.test.ts --ignore **/*.spec.js --ignore **/*.spec.ts --ignore **/*.test.jsx --ignore **/*.test.tsx --ignore **/*.spec.jsx --ignore **/*.spec.tsx`,
46
- { shell: true, stdio: 'inherit' },
47
- );
44
+ await esBuild({ srcPath, commonJS });
48
45
  if (emitModuleType) {
49
46
  const sideEffects = await getSideEffects();
50
47
  await createPackageJson(
@@ -58,7 +55,7 @@ async function nodeBuild({ srcPath, commonJS, emitModuleType, target }) {
58
55
  async function pack({ production, target, module, srcPath, emitModuleType }) {
59
56
  logInfo('Build in-progress...');
60
57
  await exec('rimraf ./dist');
61
- if (isTypeScripEnabled()) {
58
+ if (isTypeScriptEnabled()) {
62
59
  await compileTypeScript();
63
60
  }
64
61
  if (target !== 'node') await webBuild(production);
@@ -79,7 +76,6 @@ async function pack({ production, target, module, srcPath, emitModuleType }) {
79
76
  srcPath,
80
77
  commonJS: false,
81
78
  emitModuleType,
82
- target,
83
79
  });
84
80
  }
85
81
  }
@@ -118,7 +114,6 @@ exports.builder = {
118
114
  },
119
115
  emitModuleType: {
120
116
  type: 'boolean',
121
- // eslint-disable-next-line max-lines
122
117
  default: true,
123
118
  description:
124
119
  'creates type attribute in the package.json and sets its value to commonjs or module based on module cli argument. default: true',
@@ -10,7 +10,7 @@ async function buildStoryBook(isDoc = false) {
10
10
 
11
11
  async function startStoryBook(isDoc = false) {
12
12
  await exec(
13
- `cross-env FORCE_COLOR=true start-storybook ${
13
+ `cross-env NODE_ENV=development STORYBOOK_BUILD=true FORCE_COLOR=true start-storybook ${
14
14
  isDoc && '--docs'
15
15
  } -p 11000 --quiet`,
16
16
  );
@@ -5,7 +5,9 @@ const { lintCSS, lintJS } = require('./lint');
5
5
  const { CI = false } = process.env;
6
6
 
7
7
  async function test(commandOptions) {
8
- await exec(`cross-env FORCE_COLOR=true NODE_ENV=test jest ${commandOptions}`);
8
+ await exec(
9
+ `cross-env FORCE_COLOR=true NODE_OPTIONS=--experimental-vm-modules NODE_ENV=test jest ${commandOptions}`,
10
+ );
9
11
  }
10
12
 
11
13
  // eslint-disable-next-line max-statements
@@ -13,7 +15,8 @@ async function handler(argv) {
13
15
  let commandOptions = '--coverage';
14
16
  if (argv.fix) commandOptions = '-u';
15
17
  else if (argv.watch) commandOptions = '--watchAll';
16
- if (CI) commandOptions += ' --ci';
18
+ if (CI) commandOptions += ' --ci --runInBand';
19
+ else commandOptions += ' --maxWorkers=50%';
17
20
  if (argv.p) commandOptions += ' --passWithNoTests';
18
21
  if (argv.r) commandOptions += ' --bail --findRelatedTests';
19
22
  if (argv.s) commandOptions += ' --silent';
package/lib/esbuild.js ADDED
@@ -0,0 +1,62 @@
1
+ const esbuild = require('esbuild');
2
+ const fg = require('fast-glob');
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ const ESBUILD_TARGET = 'es2020';
7
+
8
+ const commonConfig = {
9
+ bundle: false,
10
+ target: ESBUILD_TARGET,
11
+ loader: { '.js': 'jsx' },
12
+ mainFields: ['module', 'browser', 'main'],
13
+ inject: [path.resolve(__dirname, './react-shim.js')],
14
+ };
15
+
16
+ const distFolder = 'dist';
17
+
18
+ const copyFiles = async ({ srcdir, outdir }) => {
19
+ const files = await fg([
20
+ `${srcdir}/**/*.*`,
21
+ `!${srcdir}/**/*.{js,jsx,ts,tsx,cjs,mjs}`,
22
+ ]);
23
+ files.forEach((srcFilePath) => {
24
+ const destFilePath = srcFilePath.replace(srcdir, outdir);
25
+ fs.copyFileSync(srcFilePath, destFilePath);
26
+ });
27
+ };
28
+
29
+ const build = async ({ srcPath, commonJS }) => {
30
+ const inputFiles = [
31
+ `${srcPath}/**/*.{js,jsx,ts,tsx}`,
32
+ `!${srcPath}/**/*.test.{js,jsx,ts,tsx}`,
33
+ `!${srcPath}/**/*.stories.{js,jsx,ts,tsx}`,
34
+ `!${srcPath}/**/*.endpoint.{js,jsx,ts,tsx}`,
35
+ ];
36
+ if (!commonJS) {
37
+ const outdir = `${distFolder}/es`;
38
+ const entryPoints = await fg(inputFiles);
39
+ await esbuild.build({
40
+ entryPoints,
41
+ ...commonConfig,
42
+ outdir,
43
+ format: 'esm',
44
+ });
45
+ await copyFiles({ srcdir: srcPath, outdir });
46
+ } else {
47
+ const outdir = `${distFolder}/cjs`;
48
+ const commonJSEntryPoints = await fg(
49
+ inputFiles.concat([`${srcPath}/**/*.cjs`]),
50
+ );
51
+ await esbuild.build({
52
+ entryPoints: commonJSEntryPoints,
53
+ ...commonConfig,
54
+ outdir,
55
+ format: 'cjs',
56
+ });
57
+ await copyFiles({ srcdir: srcPath, outdir });
58
+ }
59
+ };
60
+
61
+ exports.esBuild = build;
62
+ exports.ESBUILD_TARGET = ESBUILD_TARGET;
@@ -9,6 +9,7 @@ exports.baseExtends = [
9
9
  'plugin:jsdoc/recommended',
10
10
  'plugin:wdio/recommended',
11
11
  'plugin:testing-library/dom',
12
+ 'plugin:storybook/recommended',
12
13
  ];
13
14
 
14
15
  const basePlugins = ['testing-library', 'jest', 'jsdoc', 'wdio'];
@@ -112,16 +113,15 @@ const reactRules = {
112
113
  exports.reactRules = reactRules;
113
114
 
114
115
  exports.baseConfig = {
115
- parser: 'babel-eslint',
116
+ parser: '@babel/eslint-parser',
116
117
  plugins: basePlugins,
117
118
  env: {
118
119
  jest: true,
119
120
  browser: true,
120
121
  node: true,
121
- es6: true,
122
+ es2021: true,
122
123
  },
123
124
  parserOptions: {
124
- ecmaVersion: 2020,
125
125
  sourceType: 'module',
126
126
  ecmaFeatures: {
127
127
  jsx: true,
@@ -1,5 +1,5 @@
1
1
  module.exports = {
2
- '*.{ts,tsx}': ['tsc-files'],
2
+ '*.{ts,tsx}': ['tsc-files --noEmit --emitDeclarationOnly false'],
3
3
  '*.{js,ts,jsx,tsx}': [
4
4
  'npm run lint:fix',
5
5
  'npm run test:staged',
@@ -1,16 +1,9 @@
1
1
  module.exports = {
2
- processors: [
3
- [
4
- 'stylelint-processor-styled-components',
5
- {
6
- ignoreFiles: ['**/*.scss'],
7
- },
8
- ],
9
- ],
2
+ customSyntax: '@stylelint/postcss-css-in-js',
10
3
  plugins: [],
11
4
  extends: [
12
5
  'stylelint-config-recommended',
13
6
  'stylelint-config-styled-components',
14
7
  ],
15
- rules: { 'selector-type-no-unknown': null },
8
+ rules: { 'selector-type-no-unknown': null, 'no-extra-semicolons': null },
16
9
  };
@@ -0,0 +1,2 @@
1
+ import * as React from 'react';
2
+ export { React };
@@ -2,11 +2,11 @@ module.exports = {
2
2
  branches: [
3
3
  '+([0-9])?(.{+([0-9]),x}).x',
4
4
  'master',
5
- 'next',
6
5
  'next-major',
7
6
  { name: 'beta', prerelease: true },
8
7
  { name: 'alpha', prerelease: true },
9
8
  { name: 'hotfix', prerelease: true },
9
+ { name: 'next', prerelease: true },
10
10
  ],
11
11
  plugins: [
12
12
  '@semantic-release/commit-analyzer',
package/lib/server/csp.js CHANGED
@@ -12,6 +12,8 @@ const sources = [
12
12
  '*.ellielabs.com',
13
13
  'http://pdx-col.eum-appdynamics.com',
14
14
  'https://pdx-col.eum-appdynamics.com/',
15
+ 'https://www.google-analytics.com',
16
+ 'https://www.googletagmanager.com',
15
17
  ];
16
18
 
17
19
  const sendFileWithCSPNonce = ({
@@ -13,8 +13,11 @@ const { loadRoutes } = require('./util');
13
13
  const { getAssetPath } = require('../webpack/helpers');
14
14
 
15
15
  const pino = expressPinoLogger({
16
- prettyPrint: {
17
- levelFirst: true,
16
+ transport: {
17
+ target: 'pino-pretty',
18
+ options: {
19
+ colorize: true,
20
+ },
18
21
  },
19
22
  });
20
23
  pino.logger.level = 'warn';
@@ -57,13 +60,6 @@ const customHost = argv.host || process.env.HOST;
57
60
  const host = customHost || null; // Let http.Server use its default IPv6/4 host
58
61
  const prettyHost = customHost || 'localhost';
59
62
 
60
- // use the gzipped bundle
61
- app.get('*.js', (req, res, next) => {
62
- req.url += '.gz';
63
- res.set('Content-Encoding', 'gzip');
64
- next();
65
- });
66
-
67
63
  // Start your app.
68
64
  app.listen(port, host, async (err) => {
69
65
  if (err) {
@@ -1,5 +1,5 @@
1
1
  const webpack = require('webpack');
2
- const express = require('express');
2
+ const expressStaticGzip = require('express-static-gzip');
3
3
  const webpackDevMiddleware = require('webpack-dev-middleware');
4
4
  const webpackHotMiddleware = require('webpack-hot-middleware');
5
5
  const { sendFileWithCSPNonce } = require('../csp');
@@ -25,7 +25,7 @@ module.exports = function addDevMiddlewares(app, webpackConfig) {
25
25
  heartbeat: 10 * 1000,
26
26
  }),
27
27
  );
28
- app.use(express.static('cdn'));
28
+ app.use(expressStaticGzip('cdn'));
29
29
 
30
30
  const { outputFileSystem } = (middleware || {}).context || {};
31
31
 
@@ -1,6 +1,6 @@
1
1
  const path = require('path');
2
- const express = require('express');
3
2
  const compression = require('compression');
3
+ const expressStaticGzip = require('express-static-gzip');
4
4
  const { sendFileWithCSPNonce } = require('../csp');
5
5
 
6
6
  module.exports = function addProdMiddlewares(app, options) {
@@ -17,8 +17,15 @@ module.exports = function addProdMiddlewares(app, options) {
17
17
  sendFileWithCSPNonce({ outputPath, res });
18
18
  });
19
19
 
20
- app.use(publicPath, express.static(outputPath, { index: false }));
21
- app.use(express.static('cdn'));
20
+ app.use(
21
+ publicPath,
22
+ expressStaticGzip(outputPath, {
23
+ index: false,
24
+ enableBrotli: true,
25
+ orderPreference: ['br'],
26
+ }),
27
+ );
28
+ app.use(expressStaticGzip('cdn'));
22
29
 
23
30
  app.get('*', (req, res) => sendFileWithCSPNonce({ outputPath, res }));
24
31
  };
@@ -3,9 +3,9 @@ const path = require('path');
3
3
 
4
4
  const getCWD = () => process.cwd();
5
5
 
6
- const allJS = new RegExp(/\.js$/);
6
+ const allJS = /\.js$/;
7
7
 
8
- const serviceEndpoints = new RegExp(/\.endpoint\.js$/);
8
+ const serviceEndpoints = /\.endpoint\.js$/;
9
9
 
10
10
  const getFilesMatching = (filePattern) => {
11
11
  const getFiles = (dir) => {
@@ -62,6 +62,7 @@ const jestConfig = {
62
62
  '.*\\.(jpg|jpeg|png|gif|eot|otf|webp|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga|ico)$': `${getRootDir()}/lib/testing/mocks/image.js`,
63
63
  '.*\\.svg$': `${getRootDir()}/lib/testing/mocks/svg.js`,
64
64
  '.*\\.(html)$': `${getRootDir()}/lib/testing/mocks/html.js`,
65
+ '^lodash-es$': 'lodash',
65
66
  '@elliemae/pui-user-monitoring': `${getRootDir()}/lib/testing/mocks/pui-user-monitoring.js`,
66
67
  '@elliemae/pui-app-loader': `${getRootDir()}/lib/testing/mocks/pui-app-loader.js`,
67
68
  '@elliemae/pui-diagnostics': `${getRootDir()}/lib/testing/mocks/pui-diagnostics.js`,
@@ -74,7 +75,7 @@ const jestConfig = {
74
75
  snapshotSerializers: [],
75
76
  testResultsProcessor: 'jest-sonar-reporter',
76
77
  transformIgnorePatterns: [
77
- 'node_modules/(?!(@elliemae/*)/)',
78
+ 'node_modules/(?!(@elliemae/*|lodash-es/*)/)',
78
79
  'node_modules/@elliemae/em-platform-document-viewer',
79
80
  ],
80
81
  globals: {
@@ -1,3 +1,5 @@
1
- module.exports = {
2
- ReactComponent: 'IMAGE_MOCK',
3
- };
1
+ // eslint-disable-next-line no-unused-vars
2
+ import * as React from 'react';
3
+
4
+ export default 'SvgrURL';
5
+ export const ReactComponent = 'div';
@@ -1,5 +1,5 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
3
 
4
- exports.isTypeScripEnabled = () =>
4
+ exports.isTypeScriptEnabled = () =>
5
5
  fs.existsSync(path.join(process.cwd(), 'tsconfig.json'));
@@ -2,7 +2,8 @@
2
2
  const path = require('path');
3
3
  const fs = require('fs');
4
4
  const _ = require('lodash');
5
- const ImageMinimizerPlugin = require('image-minimizer-webpack-plugin');
5
+ const CompressionPlugin = require('compression-webpack-plugin');
6
+ const zlib = require('zlib');
6
7
 
7
8
  let pathSep = path.sep;
8
9
  if (pathSep === '\\')
@@ -71,13 +72,11 @@ const getAlias = () =>
71
72
  '@babel/runtime',
72
73
  'react',
73
74
  'react-dom',
74
- 'react-router-dom',
75
75
  'react-redux',
76
76
  'redux',
77
77
  'redux-saga',
78
78
  'moment',
79
79
  'lodash',
80
- 'connected-react-router',
81
80
  'styled-components',
82
81
  'immer',
83
82
  'react-dates',
@@ -89,7 +88,12 @@ const getAlias = () =>
89
88
  './node_modules',
90
89
  );
91
90
 
92
- const modulesToTranspile = ['@elliemae/pui-*', '@elliemae/ds-*', '@dnd-kit/*'];
91
+ const modulesToTranspile = [
92
+ '@elliemae/pui-*',
93
+ '@elliemae/ds-*',
94
+ '@dnd-kit/*',
95
+ '@elliemae/em-platform-document-viewer',
96
+ ];
93
97
 
94
98
  const getUserMonitoringFileName = () => {
95
99
  const libName = 'emuiUserMonitoring';
@@ -176,37 +180,42 @@ const isAppLoaderEnabled = () => process.env.APP_LOADER === 'true';
176
180
 
177
181
  const getMediaPath = () => `assets/[name].[contenthash].[ext]`;
178
182
 
179
- const getImageMinimizerPlugin = () =>
180
- new ImageMinimizerPlugin({
181
- minimizerOptions: {
182
- // Lossless optimization with custom option
183
- // Feel free to experiment with options for better result for you
184
- plugins: [
185
- ['gifsicle', { interlaced: true }],
186
- ['jpegtran', { progressive: true }],
187
- ['optipng', { optimizationLevel: 5 }],
188
- // Svgo configuration here https://github.com/svg/svgo#configuration
189
- [
190
- 'svgo',
191
- {
192
- plugins: [
193
- {
194
- name: 'preset-default',
195
- params: {
196
- overrides: {
197
- removeViewBox: false,
198
- addAttributesToSVGElement: {
199
- attributes: [{ xmlns: 'http://www.w3.org/2000/svg' }],
200
- },
201
- },
202
- },
203
- },
204
- ],
205
- },
206
- ],
207
- ],
208
- },
209
- });
183
+ const isGoogleTagManagerEnabled = () => {
184
+ const appConfig = JSON.parse(getAppConfig());
185
+ return !!appConfig?.googleTagManager;
186
+ };
187
+
188
+ const getCompressionPlugins = () => {
189
+ const commonConfig = {
190
+ test: /\.(js|css)$/,
191
+ exclude: [
192
+ /\/adrum-ext/,
193
+ /\/emuiUserMonitoring/,
194
+ /\/emuiDiagnostics/,
195
+ /\/emuiAppLoader/,
196
+ /\/encwLoader/,
197
+ ],
198
+ // we are compressing all files since in aws cloudfront edge lambda, we don't want to whitelist files that are not compressed due to below limits
199
+ minRatio: Number.MAX_SAFE_INTEGER,
200
+ };
201
+ return [
202
+ new CompressionPlugin({
203
+ filename: '[path][base].gz',
204
+ algorithm: 'gzip',
205
+ ...commonConfig,
206
+ }),
207
+ new CompressionPlugin({
208
+ filename: '[path][base].br',
209
+ algorithm: 'brotliCompress',
210
+ ...commonConfig,
211
+ compressionOptions: {
212
+ params: {
213
+ [zlib.constants.BROTLI_PARAM_QUALITY]: 11,
214
+ },
215
+ },
216
+ }),
217
+ ];
218
+ };
210
219
 
211
220
  exports.excludeNodeModulesExcept = excludeNodeModulesExcept;
212
221
  exports.getLibraryName = getLibraryName;
@@ -221,7 +230,6 @@ exports.isAppLoaderEnabled = isAppLoaderEnabled;
221
230
  exports.LATEST_VERSION = LATEST_VERSION;
222
231
  exports.getPaths = getPaths;
223
232
  exports.getMediaPath = getMediaPath;
224
- exports.getImageMinimizerPlugin = getImageMinimizerPlugin;
225
233
  exports.resolveExtensions = [
226
234
  '.wasm',
227
235
  '.mjs',
@@ -232,3 +240,5 @@ exports.resolveExtensions = [
232
240
  '.json',
233
241
  ];
234
242
  exports.mainFields = ['browser', 'module', 'main'];
243
+ exports.isGoogleTagManagerEnabled = isGoogleTagManagerEnabled;
244
+ exports.getCompressionPlugins = getCompressionPlugins;