@flatjs/evolve 1.8.1-next.66 → 1.8.1-next.67

Sign up to get free protection for your applications and to get access to all the features.
Files changed (114) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/constants.js +17 -1
  3. package/dist/create-webpack/create-externals.js +6 -1
  4. package/dist/create-webpack/create-optimization.js +29 -1
  5. package/dist/create-webpack/create-output.js +35 -1
  6. package/dist/create-webpack/create-performance.js +7 -1
  7. package/dist/create-webpack/create-plugins.js +78 -1
  8. package/dist/create-webpack/create-resolve.js +31 -1
  9. package/dist/create-webpack/create-rule-sets.js +16 -1
  10. package/dist/create-webpack/load-webpack-config.js +55 -1
  11. package/dist/create-webpack/rule-sets/constants.js +3 -1
  12. package/dist/create-webpack/rule-sets/rule-assets.js +44 -1
  13. package/dist/create-webpack/rule-sets/rule-css.js +84 -1
  14. package/dist/create-webpack/rule-sets/rule-less.js +45 -1
  15. package/dist/create-webpack/rule-sets/rule-scripts.js +27 -1
  16. package/dist/create-webpack/rule-sets/rule-svg-icon.js +25 -1
  17. package/dist/create-webpack/rule-sets/rule-utils.js +10 -1
  18. package/dist/create-webpack/types.js +1 -1
  19. package/dist/default-options.js +79 -1
  20. package/dist/define-config/define-config.js +4 -1
  21. package/dist/define-config/index.js +1 -1
  22. package/dist/dev-server/add-compiler-to-dev-server.js +47 -1
  23. package/dist/dev-server/create-app-page-route.js +11 -1
  24. package/dist/dev-server/create-dev-server-compiler-tasks.js +51 -1
  25. package/dist/dev-server/create-dev-server-entries.js +27 -1
  26. package/dist/dev-server/create-dev-server.js +23 -1
  27. package/dist/dev-server/index.js +6 -1
  28. package/dist/dev-server/middlewares/create-page-middleware.js +164 -1
  29. package/dist/dev-server/middlewares/create-public-assets-middleware.js +25 -1
  30. package/dist/dev-server/middlewares/index.js +2 -1
  31. package/dist/errors/evolve-build-error.js +10 -1
  32. package/dist/helpers/allow-px2rem-for-module.js +6 -1
  33. package/dist/helpers/assert-only-single-entry-item.js +23 -1
  34. package/dist/helpers/chunk-entry-map.js +21 -1
  35. package/dist/helpers/enable-bundle-hashname-for-module.js +6 -1
  36. package/dist/helpers/filter-actived-entries.d.ts +1 -1
  37. package/dist/helpers/filter-actived-entries.js +41 -1
  38. package/dist/helpers/get-bundle-file-name.js +23 -1
  39. package/dist/helpers/get-git-root.js +4 -1
  40. package/dist/helpers/get-html-plugin-config.d.ts +9 -1
  41. package/dist/helpers/get-html-plugin-config.js +47 -1
  42. package/dist/helpers/get-max-process-tasks.d.ts +1 -0
  43. package/dist/helpers/get-max-process-tasks.js +7 -0
  44. package/dist/helpers/get-pacakge-dir.js +13 -1
  45. package/dist/helpers/index.js +15 -1
  46. package/dist/helpers/merge-babel-options.js +45 -1
  47. package/dist/helpers/normalize-entry-map.js +38 -1
  48. package/dist/helpers/open-page.js +15 -1
  49. package/dist/helpers/print-log.js +49 -1
  50. package/dist/helpers/refresh-evolve-mock-options.js +23 -1
  51. package/dist/helpers/resolve-entry-map-input-files.js +20 -1
  52. package/dist/helpers/script-injects.js +39 -1
  53. package/dist/helpers/should-enable-react-fast-refresh.js +8 -1
  54. package/dist/helpers/split-to-multi-compiler.js +22 -1
  55. package/dist/index.js +5 -1
  56. package/dist/load-config/index.js +1 -1
  57. package/dist/load-config/load-evolve-config.js +35 -1
  58. package/dist/main/env-verify.js +21 -1
  59. package/dist/main/index.js +4 -1
  60. package/dist/main/prepare-build.d.ts +4 -8
  61. package/dist/main/prepare-build.js +38 -1
  62. package/dist/main/prepare-serve.js +36 -1
  63. package/dist/main/prepare-static.js +28 -1
  64. package/dist/main/start-build-dynamic.d.ts +2 -1
  65. package/dist/main/start-build-dynamic.js +144 -1
  66. package/dist/main/start-build-worker.d.ts +14 -0
  67. package/dist/main/start-build-worker.js +41 -0
  68. package/dist/main/start-build.d.ts +1 -8
  69. package/dist/main/start-build.js +49 -1
  70. package/dist/main/start-one-entry-build.d.ts +13 -0
  71. package/dist/main/start-one-entry-build.js +35 -0
  72. package/dist/main/start-serve.js +32 -1
  73. package/dist/main/start-static.js +16 -1
  74. package/dist/minimizer/create-minimizers.js +25 -1
  75. package/dist/minimizer/default-options.js +14 -1
  76. package/dist/minimizer/image-minimizer.js +56 -1
  77. package/dist/minimizer/index.js +1 -1
  78. package/dist/minimizer/terser-minimizer.js +15 -3
  79. package/dist/minimizer/types.js +1 -1
  80. package/dist/plugins/clean-webpack/clean-webpack-plugin.js +173 -1
  81. package/dist/plugins/clean-webpack/index.js +22 -1
  82. package/dist/plugins/define-variable/define-variable-plugin.js +21 -1
  83. package/dist/plugins/define-variable/index.js +1 -1
  84. package/dist/plugins/html-inject-scripts/plugin-html-inject-script.js +27 -1
  85. package/dist/plugins/module-federation/external-template-remotes.js +92 -1
  86. package/dist/plugins/module-federation/index.js +1 -1
  87. package/dist/plugins/module-federation/module-federation.js +98 -1
  88. package/dist/plugins/multi-html/index.js +15 -1
  89. package/dist/plugins/multi-html/multi-html-cdn-plugin.js +84 -1
  90. package/dist/plugins/multi-html/multi-html-plugin.js +70 -1
  91. package/dist/types/index.js +8 -1
  92. package/dist/types/types-ci.js +1 -1
  93. package/dist/types/types-dev-server.js +1 -1
  94. package/dist/types/types-entry-map.js +1 -1
  95. package/dist/types/types-federation.js +1 -1
  96. package/dist/types/types-loader-options.js +1 -1
  97. package/dist/types/types-modular-import.js +1 -1
  98. package/dist/types/types-multi-html.d.ts +6 -5
  99. package/dist/types/types-multi-html.js +1 -1
  100. package/dist/types/types-options.js +1 -1
  101. package/dist/types/types-plugin-options.js +1 -1
  102. package/dist/types/types-webpack.d.ts +1 -1
  103. package/dist/types/types-webpack.js +1 -1
  104. package/package.json +14 -12
  105. package/templates/html-plugin/index-dev.html +31 -38
  106. package/templates/html-plugin/index-inte.html +31 -38
  107. package/templates/html-plugin/index-inte2.html +31 -38
  108. package/templates/html-plugin/index-inte3.html +31 -38
  109. package/templates/html-plugin/index-inte4.html +31 -38
  110. package/templates/html-plugin/index-me.html +31 -38
  111. package/templates/html-plugin/index-prod.html +31 -38
  112. package/templates/html-plugin/index-rc.html +31 -38
  113. package/templates/html-plugin/index-uat.html +31 -38
  114. package/templates/module.html +40 -55
@@ -1 +1,173 @@
1
- import{rmSync}from"node:fs";import{relative}from"node:path";import{fileWalkSync}from"@armit/file-utility";import{logger}from"@flatjs/common";import{moduleName}from"../../constants.js";export class CleanWebpackPlugin{constructor(e={}){this.verbose=!0===e.verbose||!1,this.projectCwd=e.projectCwd||process.cwd(),this.cleanStaleWebpackAssets=!0!==e.cleanStaleWebpackAssets&&!1!==e.cleanStaleWebpackAssets||e.cleanStaleWebpackAssets,this.protectWebpackAssets=!0!==e.protectWebpackAssets&&!1!==e.protectWebpackAssets||e.protectWebpackAssets,this.cleanAfterEveryBuildPatterns=Array.isArray(e.cleanAfterEveryBuildPatterns)?e.cleanAfterEveryBuildPatterns:[],this.cleanOnceBeforeBuildPatterns=Array.isArray(e.cleanOnceBeforeBuildPatterns)?e.cleanOnceBeforeBuildPatterns:["**/*"],this.currentAssets=[],this.initialClean=!1,this.outputPath="",this.apply=this.apply.bind(this),this.handleInitial=this.handleInitial.bind(this),this.handleDone=this.handleDone.bind(this),this.removeFiles=this.removeFiles.bind(this)}apply(e){if(!e.options.output||!e.options.output.path)return void logger.warn("clean-webpack-plugin: options.output.path not defined. Plugin disabled...",moduleName);this.outputPath=e.options.output.path;const t=e.hooks;0!==this.cleanOnceBeforeBuildPatterns.length&&t.emit.tap("clean-webpack-plugin",(e=>{this.handleInitial(e)})),t.done.tap("clean-webpack-plugin",(e=>{this.handleDone(e)}))}handleInitial(e){if(this.initialClean)return;e.getStats().hasErrors()||(this.initialClean=!0,this.removeFiles(this.cleanOnceBeforeBuildPatterns))}handleDone(e){if(e.hasErrors())return void(this.verbose&&logger.warn("clean-webpack-plugin: pausing due to webpack errors",moduleName));const t=(e.toJson({assets:!0}).assets||[]).map((e=>e.name)),s=this.currentAssets.filter((e=>!1===t.includes(e)));this.currentAssets=t.sort();const i=[];!0===this.cleanStaleWebpackAssets&&0!==s.length&&i.push(...s),0!==this.cleanAfterEveryBuildPatterns.length&&i.push(...this.cleanAfterEveryBuildPatterns),0!==i.length&&this.removeFiles(i)}removeFiles(e){try{const t=fileWalkSync(e,{absolute:!0,unique:!0,cwd:this.outputPath,dot:!0,ignore:this.protectWebpackAssets?this.currentAssets:[]});for(const e of t)rmSync(e,{force:!0,recursive:!0});this.verbose&&t.forEach((e=>{const t=relative(this.projectCwd,e);logger.debug(`clean-webpack-plugin: removed ${t}`,moduleName)}))}catch(e){if(/Cannot delete files\/folders outside the current working directory\./.test(e.message)){throw new Error("clean-webpack-plugin: Cannot delete files/folders outside the current working directory. Can be overridden with the `dangerouslyAllowCleanPatternsOutsideProject` option.")}throw e}}}
1
+ import { rmSync } from 'node:fs';
2
+ import { relative } from 'node:path';
3
+ import { fileWalkSync } from '@armit/file-utility';
4
+ import { logger } from '@flatjs/common';
5
+ import { moduleName } from '../../constants.js';
6
+ export class CleanWebpackPlugin {
7
+ constructor(options = {}) {
8
+ this.verbose = options.verbose === true || false;
9
+ this.projectCwd = options.projectCwd || process.cwd();
10
+ this.cleanStaleWebpackAssets =
11
+ options.cleanStaleWebpackAssets === true ||
12
+ options.cleanStaleWebpackAssets === false
13
+ ? options.cleanStaleWebpackAssets
14
+ : true;
15
+ this.protectWebpackAssets =
16
+ options.protectWebpackAssets === true ||
17
+ options.protectWebpackAssets === false
18
+ ? options.protectWebpackAssets
19
+ : true;
20
+ this.cleanAfterEveryBuildPatterns = Array.isArray(options.cleanAfterEveryBuildPatterns)
21
+ ? options.cleanAfterEveryBuildPatterns
22
+ : [];
23
+ this.cleanOnceBeforeBuildPatterns = Array.isArray(options.cleanOnceBeforeBuildPatterns)
24
+ ? options.cleanOnceBeforeBuildPatterns
25
+ : ['**/*'];
26
+ /**
27
+ * Store webpack build assets
28
+ */
29
+ this.currentAssets = [];
30
+ /**
31
+ * Only used with cleanOnceBeforeBuildPatterns
32
+ */
33
+ this.initialClean = false;
34
+ this.outputPath = '';
35
+ this.apply = this.apply.bind(this);
36
+ this.handleInitial = this.handleInitial.bind(this);
37
+ this.handleDone = this.handleDone.bind(this);
38
+ this.removeFiles = this.removeFiles.bind(this);
39
+ }
40
+ apply(compiler) {
41
+ if (!compiler.options.output || !compiler.options.output.path) {
42
+ logger.warn('clean-webpack-plugin: options.output.path not defined. Plugin disabled...', moduleName);
43
+ return;
44
+ }
45
+ this.outputPath = compiler.options.output.path;
46
+ /**
47
+ * webpack 4+ comes with a new plugin system.
48
+ *
49
+ * Check for hooks in-order to support old plugin system
50
+ */
51
+ const hooks = compiler.hooks;
52
+ if (this.cleanOnceBeforeBuildPatterns.length !== 0) {
53
+ hooks.emit.tap('clean-webpack-plugin', (compilation) => {
54
+ this.handleInitial(compilation);
55
+ });
56
+ }
57
+ hooks.done.tap('clean-webpack-plugin', (stats) => {
58
+ this.handleDone(stats);
59
+ });
60
+ }
61
+ /**
62
+ * Initially remove files from output directory prior to build.
63
+ *
64
+ * Only happens once.
65
+ *
66
+ * Warning: It is recommended to initially clean your build directory outside of webpack to minimize unexpected behavior.
67
+ */
68
+ handleInitial(compilation) {
69
+ if (this.initialClean) {
70
+ return;
71
+ }
72
+ /**
73
+ * Do not remove files if there are compilation errors
74
+ *
75
+ * Handle logging inside this.handleDone
76
+ */
77
+ const stats = compilation.getStats();
78
+ if (stats.hasErrors()) {
79
+ return;
80
+ }
81
+ this.initialClean = true;
82
+ this.removeFiles(this.cleanOnceBeforeBuildPatterns);
83
+ }
84
+ handleDone(stats) {
85
+ /**
86
+ * Do nothing if there is a webpack error
87
+ */
88
+ if (stats.hasErrors()) {
89
+ if (this.verbose) {
90
+ logger.warn('clean-webpack-plugin: pausing due to webpack errors', moduleName);
91
+ }
92
+ return;
93
+ }
94
+ /**
95
+ * Fetch Webpack's output asset files
96
+ */
97
+ const assets = stats.toJson({
98
+ assets: true,
99
+ }).assets || [];
100
+ const assetList = assets.map((asset) => {
101
+ return asset.name;
102
+ });
103
+ /**
104
+ * Get all files that were in the previous build but not the current
105
+ *
106
+ * (relies on del's cwd: outputPath option)
107
+ */
108
+ const staleFiles = this.currentAssets.filter((previousAsset) => {
109
+ return assetList.includes(previousAsset) === false;
110
+ });
111
+ /**
112
+ * Save assets for next compilation
113
+ */
114
+ this.currentAssets = assetList.sort();
115
+ const removePatterns = [];
116
+ /**
117
+ * Remove unused webpack assets
118
+ */
119
+ if (this.cleanStaleWebpackAssets === true && staleFiles.length !== 0) {
120
+ removePatterns.push(...staleFiles);
121
+ }
122
+ /**
123
+ * Remove cleanAfterEveryBuildPatterns
124
+ */
125
+ if (this.cleanAfterEveryBuildPatterns.length !== 0) {
126
+ removePatterns.push(...this.cleanAfterEveryBuildPatterns);
127
+ }
128
+ if (removePatterns.length !== 0) {
129
+ this.removeFiles(removePatterns);
130
+ }
131
+ }
132
+ removeFiles(patterns) {
133
+ try {
134
+ const deleted = fileWalkSync(patterns, {
135
+ absolute: true,
136
+ unique: true,
137
+ // Change context to build directory
138
+ cwd: this.outputPath,
139
+ dot: true,
140
+ ignore: this.protectWebpackAssets ? this.currentAssets : [],
141
+ });
142
+ for (const filepath of deleted) {
143
+ rmSync(filepath, {
144
+ force: true,
145
+ recursive: true,
146
+ });
147
+ }
148
+ /**
149
+ * Log if verbose is enabled
150
+ */
151
+ if (this.verbose) {
152
+ deleted.forEach((file) => {
153
+ const filename = relative(this.projectCwd, file);
154
+ const message = 'removed';
155
+ /**
156
+ * Use console.warn over .log
157
+ * https://github.com/webpack/webpack/issues/1904
158
+ * https://github.com/johnagan/clean-webpack-plugin/issues/11
159
+ */
160
+ logger.debug(`clean-webpack-plugin: ${message} ${filename}`, moduleName);
161
+ });
162
+ }
163
+ }
164
+ catch (error) {
165
+ const needsForce = /Cannot delete files\/folders outside the current working directory\./.test(error.message);
166
+ if (needsForce) {
167
+ const message = 'clean-webpack-plugin: Cannot delete files/folders outside the current working directory. Can be overridden with the `dangerouslyAllowCleanPatternsOutsideProject` option.';
168
+ throw new Error(message);
169
+ }
170
+ throw error;
171
+ }
172
+ }
173
+ }
@@ -1 +1,22 @@
1
- import{join}from"node:path";import{ensureSlash}from"@flatjs/common";import{CleanWebpackPlugin}from"./clean-webpack-plugin.js";export const createCleanWebpackPlugin=(e,n,o)=>e?[]:[new CleanWebpackPlugin({verbose:!0,projectCwd:o.projectCwd,cleanOnceBeforeBuildPatterns:[`${join(ensureSlash(n[0],!0),"**/*")}`]})];
1
+ import { join } from 'node:path';
2
+ import { ensureSlash } from '@flatjs/common';
3
+ import { CleanWebpackPlugin } from './clean-webpack-plugin.js';
4
+ /**
5
+ * Cleaning up the /dist folder for `production` build
6
+ * @param singleEntryItem
7
+ * @returns
8
+ */
9
+ export const createCleanWebpackPlugin = (serveMode, entryMapItem, evolveOptions) => {
10
+ if (serveMode) {
11
+ return [];
12
+ }
13
+ return [
14
+ new CleanWebpackPlugin({
15
+ verbose: true,
16
+ projectCwd: evolveOptions.projectCwd,
17
+ cleanOnceBeforeBuildPatterns: [
18
+ `${join(ensureSlash(entryMapItem[0], true), '**/*')}`,
19
+ ],
20
+ }),
21
+ ];
22
+ };
@@ -1 +1,21 @@
1
- import{getLastCommitHash,gitBranchName}from"@armit/git";import webpack from"webpack";export const createBuiltinDefineVariables=async(e,i)=>{const t=await getLastCommitHash(),n=await gitBranchName();return[new webpack.DefinePlugin({__SENTRY_DEBUG__:e,"process.env.FLAT_BUILD_DATE":JSON.stringify((new Date).toISOString()),"process.env.FLAT_COMMIT_HASH":JSON.stringify(t),"process.env.FLAT_BRANCH_NAME":JSON.stringify(n),...i.pluginOptions.definePlugin})]};
1
+ /* eslint-disable @typescript-eslint/naming-convention */
2
+ import { getLastCommitHash, gitBranchName } from '@armit/git';
3
+ import webpack from 'webpack';
4
+ /**
5
+ * The DefinePlugin replaces variables in your code with other values or expressions at compile time.
6
+ * `__SENTRY_DEBUG__`,`process.env.FLAT_BUILD_DATE`, `process.env.FLAT_COMMIT_HASH`, `process.env.FLAT_BRANCH_NAME`
7
+ * @returns
8
+ */
9
+ export const createBuiltinDefineVariables = async (serveMode, evolveOptions) => {
10
+ const commitHash = await getLastCommitHash();
11
+ const branchName = await gitBranchName();
12
+ return [
13
+ new webpack.DefinePlugin({
14
+ __SENTRY_DEBUG__: serveMode,
15
+ 'process.env.FLAT_BUILD_DATE': JSON.stringify(new Date().toISOString()),
16
+ 'process.env.FLAT_COMMIT_HASH': JSON.stringify(commitHash),
17
+ 'process.env.FLAT_BRANCH_NAME': JSON.stringify(branchName),
18
+ ...evolveOptions.pluginOptions.definePlugin,
19
+ }),
20
+ ];
21
+ };
@@ -1 +1 @@
1
- export*from"./define-variable-plugin.js";
1
+ export * from './define-variable-plugin.js';
@@ -1 +1,27 @@
1
- import htmlWebpackPlugin from"html-webpack-plugin";const PLUGIN_PREFIX="HtmlInjectScriptPlugin";export class HtmlInjectScriptPlugin{constructor(t){this.scripts=t||[]}processScripts(){return this.scripts.filter(Boolean).map((t=>({tagName:"script",innerHTML:t,voidTag:!1,attributes:{},meta:{plugin:"html-inject-script-webpack-plugin"}})))}apply(t){t.hooks.compilation.tap(`${PLUGIN_PREFIX}_compilation`,(t=>{htmlWebpackPlugin.getHooks(t).alterAssetTags.tap(`${PLUGIN_PREFIX}_alterAssetTags`,(t=>(t.assetTags.scripts.unshift(...this.processScripts()),t)))}))}}
1
+ import htmlWebpackPlugin from 'html-webpack-plugin';
2
+ const PLUGIN_PREFIX = `HtmlInjectScriptPlugin`;
3
+ export class HtmlInjectScriptPlugin {
4
+ constructor(scripts) {
5
+ this.scripts = scripts || [];
6
+ }
7
+ processScripts() {
8
+ return this.scripts.filter(Boolean).map((asset) => {
9
+ return {
10
+ tagName: 'script',
11
+ innerHTML: asset,
12
+ voidTag: false,
13
+ attributes: {},
14
+ meta: { plugin: 'html-inject-script-webpack-plugin' },
15
+ };
16
+ });
17
+ }
18
+ apply(compiler) {
19
+ compiler.hooks.compilation.tap(`${PLUGIN_PREFIX}_compilation`, (compilation) => {
20
+ const hooks = htmlWebpackPlugin.getHooks(compilation);
21
+ hooks.alterAssetTags.tap(`${PLUGIN_PREFIX}_alterAssetTags`, (data) => {
22
+ data.assetTags.scripts.unshift(...this.processScripts());
23
+ return data;
24
+ });
25
+ });
26
+ }
27
+ }
@@ -1 +1,92 @@
1
- import webpackSources from"webpack-sources";const PLUGIN_NAME="ExternalTemplateRemotesPlugin",isExternalModule=e=>"ExternalModule"===e.constructor.name;function extractUrlAndGlobal(e){const t=e.indexOf("@");if(t<=0||t===e.length-1)throw new Error(`Invalid request "${e}"`);return[e.substring(t+1),e.substring(0,t)]}export class ExternalTemplateRemotesPlugin{apply(e){e.hooks.make.tap(PLUGIN_NAME,(e=>{const t=[];e.hooks.buildModule.tap(PLUGIN_NAME,(e=>{isExternalModule(e)&&"script"===e.externalType&&t.push(e)})),e.hooks.afterCodeGeneration.tap(PLUGIN_NAME,(()=>{t.forEach((t=>{const o=extractUrlAndGlobal(t.request)[0],n=toExpression(o),r=e.codeGenerationResults.get(t,void 0).sources,s=r.get("javascript");if(s){const e=new webpackSources.RawSource(s.source().toString().replace(`"${o}"`,n));r.set("javascript",e)}}))}))}))}}function toExpression(e){const t=[],o=[];let n=!1,r=!1;for(const s of e)if("["===s){if(n){r=!0;break}n=!0,o.length&&(t.push(`"${o.join("")}"`),o.length=0)}else if("]"===s){if(!n){r=!0;break}n=!1,o.length&&(t.push(`${o.join("")}`),o.length=0),o.length=0}else o.push(s);if(n||r)throw new Error(`Invalid template URL "${e}"`);return o.length&&t.push(`"${o.join("")}"`),t.join(" + ")}
1
+ import webpackSources from 'webpack-sources';
2
+ const PLUGIN_NAME = 'ExternalTemplateRemotesPlugin';
3
+ const isExternalModule = (module) => {
4
+ return module.constructor.name === 'ExternalModule';
5
+ };
6
+ /**
7
+ * @param {string} urlAndGlobal the script request
8
+ * @returns {string[]} script url and its global variable
9
+ */
10
+ function extractUrlAndGlobal(urlAndGlobal) {
11
+ const index = urlAndGlobal.indexOf('@');
12
+ if (index <= 0 || index === urlAndGlobal.length - 1) {
13
+ throw new Error(`Invalid request "${urlAndGlobal}"`);
14
+ }
15
+ return [urlAndGlobal.substring(index + 1), urlAndGlobal.substring(0, index)];
16
+ }
17
+ export class ExternalTemplateRemotesPlugin {
18
+ apply(compiler) {
19
+ compiler.hooks.make.tap(PLUGIN_NAME, (compilation) => {
20
+ const scriptExternalModules = [];
21
+ compilation.hooks.buildModule.tap(PLUGIN_NAME, (module) => {
22
+ if (isExternalModule(module) && module.externalType === 'script') {
23
+ scriptExternalModules.push(module);
24
+ }
25
+ });
26
+ compilation.hooks.afterCodeGeneration.tap(PLUGIN_NAME, () => {
27
+ scriptExternalModules.forEach((module) => {
28
+ const urlTemplate = extractUrlAndGlobal(module.request)[0];
29
+ const urlExpression = toExpression(urlTemplate);
30
+ const sourceMap = compilation.codeGenerationResults.get(module, undefined).sources;
31
+ const rawSource = sourceMap.get('javascript');
32
+ if (rawSource) {
33
+ const source = new webpackSources.RawSource(rawSource
34
+ .source()
35
+ .toString()
36
+ .replace(`"${urlTemplate}"`, urlExpression));
37
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
38
+ sourceMap.set('javascript', source);
39
+ }
40
+ });
41
+ });
42
+ });
43
+ }
44
+ }
45
+ /**
46
+ * app2@localhost/remoteEntry.js --> "\"app2@localhost/remoteEntry.js\""
47
+ * app2@[window.app2Url]/remoteEntry.js --> "\"app2@\" + window.app2Url + \"/remoteEntry.js\""
48
+ * @param templateUrl
49
+ * @returns
50
+ */
51
+ // eslint-disable-next-line sonarjs/cognitive-complexity
52
+ function toExpression(templateUrl) {
53
+ const result = [];
54
+ const current = [];
55
+ let isExpression = false;
56
+ let invalid = false;
57
+ for (const c of templateUrl) {
58
+ if (c === '[') {
59
+ if (isExpression) {
60
+ invalid = true;
61
+ break;
62
+ }
63
+ isExpression = true;
64
+ if (current.length) {
65
+ result.push(`"${current.join('')}"`);
66
+ current.length = 0;
67
+ }
68
+ }
69
+ else if (c === ']') {
70
+ if (!isExpression) {
71
+ invalid = true;
72
+ break;
73
+ }
74
+ isExpression = false;
75
+ if (current.length) {
76
+ result.push(`${current.join('')}`);
77
+ current.length = 0;
78
+ }
79
+ current.length = 0;
80
+ }
81
+ else {
82
+ current.push(c);
83
+ }
84
+ }
85
+ if (isExpression || invalid) {
86
+ throw new Error(`Invalid template URL "${templateUrl}"`);
87
+ }
88
+ if (current.length) {
89
+ result.push(`"${current.join('')}"`);
90
+ }
91
+ return result.join(' + ');
92
+ }
@@ -1 +1 @@
1
- export*from"./module-federation.js";
1
+ export * from './module-federation.js';
@@ -1 +1,98 @@
1
- import{join}from"node:path";import{ensureSlash}from"@flatjs/common";import webpack from"webpack";import{normalizeEvolveEntryName}from"../../helpers/normalize-entry-map.js";import{injectFederationScripts}from"../../helpers/script-injects.js";import{HtmlInjectScriptPlugin}from"../html-inject-scripts/plugin-html-inject-script.js";import{ExternalTemplateRemotesPlugin}from"./external-template-remotes.js";const normalizeWidgetName=(e="")=>e.replace(/[/-]/g,"_").toLowerCase(),remoteFileName=e=>join(e,"micro-remote-module.js");export const createModuleFederationPlugin=(e,t,r)=>{const o=r.projectVirtualPath,[n,i]=t,m=r.multiHtmlCdn,a=r.multiHtmlCdnEnvResolver,l=i.options?.moduleFederation,s=[];if(l){const{remotes:t,exposes:r,...i}=l,p=remoteFileName(n),c=normalizeWidgetName(n),u=(r?Array.isArray(r)?r:[r]:[]).map((e=>{const t={};for(const[r,o]of Object.entries(e))t[r]={...o,name:join(n,o.name.replace(/^\//,""))};return t})),d=(t||[]).map((({name:e,endpoint:t})=>{const r=normalizeEvolveEntryName(e,o),n=normalizeWidgetName(r),i=remoteFileName(r);return{[n]:`${n}@${t?ensureSlash(t(e,r),!1):"[window.evolveFetchMicroWidgets()]"}/${i}`}}));s.push(new webpack.container.ModuleFederationPlugin({name:c,filename:p,remotes:d,exposes:u,...i}),new ExternalTemplateRemotesPlugin),e||s.unshift(new HtmlInjectScriptPlugin([injectFederationScripts(m,a)]))}return s};
1
+ import { join } from 'node:path';
2
+ import { ensureSlash } from '@flatjs/common';
3
+ import webpack from 'webpack';
4
+ import { normalizeEvolveEntryName } from '../../helpers/normalize-entry-map.js';
5
+ import { injectFederationScripts } from '../../helpers/script-injects.js';
6
+ import { HtmlInjectScriptPlugin } from '../html-inject-scripts/plugin-html-inject-script.js';
7
+ import { ExternalTemplateRemotesPlugin } from './external-template-remotes.js';
8
+ /**
9
+ * `${projectVirtualPath}/mine` --> evolve_demo_mine
10
+ * @param entryPath `${projectVirtualPath}/mine`
11
+ */
12
+ const normalizeWidgetName = (entryPath = '') => {
13
+ return entryPath.replace(/[/-]/g, '_').toLowerCase();
14
+ };
15
+ const remoteFileName = (entryName) => {
16
+ return join(entryName, `micro-remote-module.js`);
17
+ };
18
+ export const createModuleFederationPlugin = (serveMode, entryMapItem, evolveOptions) => {
19
+ const projectVirtualPath = evolveOptions.projectVirtualPath;
20
+ const [entryName, entryConfig] = entryMapItem;
21
+ const multiCdnConfig = evolveOptions.multiHtmlCdn;
22
+ const multiHtmlCdnResolver = evolveOptions.multiHtmlCdnEnvResolver;
23
+ const moduleFederation = entryConfig.options?.moduleFederation;
24
+ const plugins = [];
25
+ if (moduleFederation) {
26
+ const { remotes, exposes, ...restFederationOptions } = moduleFederation;
27
+ // e.g. `flatjs/evolve/mine` => `flatjs/evolve/home/micro-remote-module.js`
28
+ const entryRemoteFileName = remoteFileName(entryName);
29
+ // e.g. `flatjs/evolve/mine` => `flatjs_evolve_mine`
30
+ const containerName = normalizeWidgetName(entryName);
31
+ const patchExposes = exposes
32
+ ? Array.isArray(exposes)
33
+ ? exposes
34
+ : [exposes]
35
+ : [];
36
+ const myExposes = patchExposes.map((s) => {
37
+ const exposeItem = {};
38
+ for (const [key, config] of Object.entries(s)) {
39
+ exposeItem[key] = {
40
+ ...config,
41
+ name: join(entryName, config.name.replace(/^\//, '')),
42
+ };
43
+ }
44
+ return exposeItem;
45
+ });
46
+ const myRemotes = (remotes || []).map(({ name, endpoint }) => {
47
+ // e.g. `flatjs/evolve/home`
48
+ const normalizedEntryName = normalizeEvolveEntryName(name, projectVirtualPath);
49
+ // e.g. `flatjs_evolve_home`
50
+ const remoteWidgetName = normalizeWidgetName(normalizedEntryName);
51
+ // e.g. `flatjs/evolve/home/micro-remote-module.js`
52
+ const refRemoteEntryFileName = remoteFileName(normalizedEntryName);
53
+ // construct endpoint for remote widget name.
54
+ const endpointPath = endpoint
55
+ ? ensureSlash(endpoint(name, normalizedEntryName), false)
56
+ : `[window.evolveFetchMicroWidgets()]`;
57
+ return {
58
+ [remoteWidgetName]: `${remoteWidgetName}@${endpointPath}/${refRemoteEntryFileName}`,
59
+ };
60
+ });
61
+ plugins.push(
62
+ // https://webpack.js.org/plugins/module-federation-plugin/
63
+ new webpack.container.ModuleFederationPlugin({
64
+ /**
65
+ * The name of the container
66
+ * `${projectName}-${moduleName}` e.g. `flatjs_evolve_home`
67
+ */
68
+ name: containerName,
69
+ /**
70
+ * The filename of the container as relative path inside the `output.path` directory.
71
+ * `${entryName}/micro-remote-module.js`, e.g. `flatjs/evolve/home/micro-remote-module.js`
72
+ */
73
+ filename: entryRemoteFileName,
74
+ /**
75
+ * Container locations and request scopes from which modules should be resolved and loaded at runtime.
76
+ * When provided, property name is used as request scope, otherwise request scope is automatically inferred from container location.
77
+ */
78
+ remotes: myRemotes,
79
+ /**
80
+ * Modules that should be exposed by this container.
81
+ * When provided, property name is used as public name, otherwise public name is automatically inferred from request.
82
+ */
83
+ exposes: myExposes,
84
+ /**
85
+ * Options for library.
86
+ * library: { type: 'var', name: containerName },
87
+ * other module federation configurations
88
+ */
89
+ ...restFederationOptions,
90
+ }), new ExternalTemplateRemotesPlugin());
91
+ if (!serveMode) {
92
+ plugins.unshift(new HtmlInjectScriptPlugin([
93
+ injectFederationScripts(multiCdnConfig, multiHtmlCdnResolver),
94
+ ]));
95
+ }
96
+ }
97
+ return plugins;
98
+ };
@@ -1 +1,15 @@
1
- import{FlatEvolveMultiCdnPlugin}from"./multi-html-cdn-plugin.js";import{createMultiHtmlWebpackPlugin}from"./multi-html-plugin.js";export const createHtmlPlugins=(t,l,u)=>{const i=[];if(t)return i;const n=Object.keys(u.multiHtmlCdn);return i.push(...createMultiHtmlWebpackPlugin(t,u,l,n)),i.push(new FlatEvolveMultiCdnPlugin(u)),i};
1
+ import { FlatEvolveMultiCdnPlugin } from './multi-html-cdn-plugin.js';
2
+ import { createMultiHtmlWebpackPlugin } from './multi-html-plugin.js';
3
+ export const createHtmlPlugins = (serveMode, entryMapItem, evolveOptions) => {
4
+ const plugins = [];
5
+ // Only for `production`
6
+ if (serveMode) {
7
+ return plugins;
8
+ }
9
+ // Attach `html-webpack-plugin` first
10
+ const allEnv = Object.keys(evolveOptions.multiHtmlCdn);
11
+ plugins.push(...createMultiHtmlWebpackPlugin(serveMode, evolveOptions, entryMapItem, allEnv));
12
+ // Attach `@flatjs/evolve-plugin-multi-html-cdn`
13
+ plugins.push(new FlatEvolveMultiCdnPlugin(evolveOptions));
14
+ return plugins;
15
+ };
@@ -1 +1,84 @@
1
- import{basename}from"node:path";import HtmlWebpackPlugin from"html-webpack-plugin";import webpack from"webpack";import{cdnFinder,findEnvCdn,httpUrlJoin}from"../../helpers/script-injects.js";export class FlatEvolveMultiCdnPlugin{constructor(e){if(this.pluginName="FlatEvolveMultiCdnPlugin",this.requireFn=webpack.RuntimeGlobals.publicPath,this.config=e.multiHtmlCdn,this.cdnResolver=e.multiHtmlCdnEnvResolver||function cdnResolver(){},!this.config?.prod)throw new Error("We must setup `prod` for each CDN config node!")}apply(e){e.hooks.thisCompilation.tap(this.pluginName,(e=>{e.mainTemplate.hooks.requireExtensions.tap(this.pluginName,((n,t)=>{const i=[];i.push("// Dynamic assets path override(`@flatjs/evolve`) plugin-multi-html-cdn`)");const s=e.chunkGraph?.getTreeRuntimeRequirements(t);return s&&s.has(webpack.RuntimeGlobals.requireScope)&&(i.push("(function () {"),i.push(webpack.Template.indent("var flatjsMultiCdn = {")),i.push(webpack.Template.indent(webpack.Template.indent([`cdnConfig: ${JSON.stringify(this.config||{})},`]))),i.push(webpack.Template.indent(webpack.Template.indent([`cdnResolver: ${this.cdnResolver.toString()},`]))),i.push(webpack.Template.indent(webpack.Template.indent([`cdnFinder: ${cdnFinder.toString()}`]))),i.push(webpack.Template.indent("};")),i.push(webpack.Template.indent(`${this.requireFn} = flatjsMultiCdn.cdnFinder(flatjsMultiCdn.cdnConfig, flatjsMultiCdn.cdnResolver) || ${this.requireFn};`)),i.push("})();")),webpack.Template.asString(i)}))})),e.hooks.compilation.tap(this.pluginName,(e=>{HtmlWebpackPlugin.getHooks(e).beforeAssetTagGeneration.tap(this.pluginName,(e=>{const{assets:n}=e,{userOptions:t}=e.plugin,i=t.multiCdn,s=n.publicPath,a=n.js.map((e=>{if(i.disabled)return basename(e);const n=findEnvCdn(this.config,i.env);return httpUrlJoin(n,e.replace(s,""))})),p=n.css.map((e=>{if(i.disabled)return basename(e);const n=findEnvCdn(this.config,i.env);return httpUrlJoin(n,e.replace(s,""))}));return e.assets.js=a,e.assets.css=p,e}))}))}}
1
+ import { basename } from 'node:path';
2
+ import HtmlWebpackPlugin from 'html-webpack-plugin';
3
+ import webpack from 'webpack';
4
+ import { cdnFinder, findEnvCdn, httpUrlJoin, } from '../../helpers/script-injects.js';
5
+ export class FlatEvolveMultiCdnPlugin {
6
+ constructor(evolveOptions) {
7
+ this.pluginName = 'FlatEvolveMultiCdnPlugin';
8
+ // https://github.com/webpack/webpack/blob/3d653290fafe385277b48e5a36807124618b9561/lib/MainTemplate.js#L12
9
+ // the bundle public path RuntimeGlobals.publicPath: '__webpack_require__.p';
10
+ this.requireFn = webpack.RuntimeGlobals.publicPath;
11
+ this.config = evolveOptions.multiHtmlCdn;
12
+ this.cdnResolver =
13
+ evolveOptions.multiHtmlCdnEnvResolver ||
14
+ function cdnResolver() {
15
+ return undefined;
16
+ };
17
+ // Make sure we have `prod` configuration for each cdn node at least.
18
+ if (!this.config?.prod) {
19
+ throw new Error('We must setup `prod` for each CDN config node!');
20
+ }
21
+ }
22
+ /**
23
+ * Apply the plugin to check if there are non initial chunks which need to be imported using `require-ensure` or `import`
24
+ * https://github.com/webpack/webpack/blob/3d653290fafe385277b48e5a36807124618b9561/lib/MainTemplate.js#L158
25
+ * https://www.npmjs.com/package/vscode-webpack-debugger
26
+ * https://www.cnblogs.com/Scar007/p/9166068.html
27
+ * https://www.cnblogs.com/pluslius/p/10271537.html
28
+ */
29
+ apply(compiler) {
30
+ // Handle chunk assets while `Compilation:before-chunk-assets`
31
+ // https://github.com/webpack/webpack/blob/3d653290fafe385277b48e5a36807124618b9561/lib/MainTemplate.js#L58
32
+ compiler.hooks.thisCompilation.tap(this.pluginName, (compilation) => {
33
+ compilation.mainTemplate.hooks.requireExtensions.tap(this.pluginName, (_source, chunk) => {
34
+ const buf = [];
35
+ buf.push('// Dynamic assets path override(`@flatjs/evolve`) plugin-multi-html-cdn`)');
36
+ const runtimeRequirements = compilation.chunkGraph?.getTreeRuntimeRequirements(chunk);
37
+ if (runtimeRequirements &&
38
+ runtimeRequirements.has(webpack.RuntimeGlobals.requireScope)) {
39
+ buf.push('(function () {');
40
+ buf.push(webpack.Template.indent(`var flatjsMultiCdn = {`));
41
+ buf.push(webpack.Template.indent(webpack.Template.indent([
42
+ `cdnConfig: ${JSON.stringify(this.config || {})},`,
43
+ ])));
44
+ buf.push(webpack.Template.indent(webpack.Template.indent([
45
+ `cdnResolver: ${this.cdnResolver.toString()},`,
46
+ ])));
47
+ buf.push(webpack.Template.indent(webpack.Template.indent([`cdnFinder: ${cdnFinder.toString()}`])));
48
+ buf.push(webpack.Template.indent(`};`));
49
+ buf.push(webpack.Template.indent(`${this.requireFn} = flatjsMultiCdn.cdnFinder(flatjsMultiCdn.cdnConfig, flatjsMultiCdn.cdnResolver) || ${this.requireFn};`));
50
+ buf.push('})();');
51
+ }
52
+ return webpack.Template.asString(buf);
53
+ });
54
+ });
55
+ // Using html webpack plugin hooks to replace `scripts` `styles` before inject to html temlate file.
56
+ compiler.hooks.compilation.tap(this.pluginName, (compilation) => {
57
+ HtmlWebpackPlugin.getHooks(compilation).beforeAssetTagGeneration.tap(this.pluginName, (data) => {
58
+ const { assets } = data;
59
+ const { userOptions } = data.plugin;
60
+ const multiCdn = userOptions.multiCdn;
61
+ const publicPath = assets.publicPath;
62
+ const scripts = assets.js.map((scriptItem) => {
63
+ // Normally for `index-dev.html` we need to use relative path.
64
+ if (multiCdn.disabled) {
65
+ return basename(scriptItem);
66
+ }
67
+ const randomCdn = findEnvCdn(this.config, multiCdn.env);
68
+ return httpUrlJoin(randomCdn, scriptItem.replace(publicPath, ''));
69
+ });
70
+ const styles = assets.css.map((styleItem) => {
71
+ // Normally for `index-dev.html` we need to use relative path.
72
+ if (multiCdn.disabled) {
73
+ return basename(styleItem);
74
+ }
75
+ const randomCdn = findEnvCdn(this.config, multiCdn.env);
76
+ return httpUrlJoin(randomCdn, styleItem.replace(publicPath, ''));
77
+ });
78
+ data.assets.js = scripts;
79
+ data.assets.css = styles;
80
+ return data;
81
+ });
82
+ });
83
+ }
84
+ }
@@ -1 +1,70 @@
1
- import HtmlWebpackPlugin from"html-webpack-plugin";import{allowPx2remForModule}from"../../helpers/allow-px2rem-for-module.js";import{getHtmlPluginConfig}from"../../helpers/get-html-plugin-config.js";const minifyOpts={minifyJS:!0,removeComments:!0,collapseWhitespace:!0,collapseBooleanAttributes:!1};export const createMultiHtmlWebpackPlugin=(e,t,i,l)=>{const[o,n]=i,r=[],{options:m}=n,g=e?"development":"production";for(const e of l)r.push(new HtmlWebpackPlugin({inject:"body",title:getHtmlPluginConfig("title",g,m?.title),chunks:[o],minify:!1!==m?.htmlMinify&&!["me","dev"].includes(e)&&minifyOpts,filename:`${o}/index${"prod"===e?"":`-${e}`}.html`,template:getHtmlPluginConfig("templatePath",g,m?.templatePath).replace("{0}",e),templateParameters:{title:getHtmlPluginConfig("title",g,m?.title),favicon:getHtmlPluginConfig("favicon",g,m?.favicon),headBeforeHtmlTags:getHtmlPluginConfig("headBeforeHtmlTags",g,m?.headBeforeHtmlTags),inlineScripts:getHtmlPluginConfig("inlineScripts",g,m?.inlineScripts),headBeforeStyles:getHtmlPluginConfig("headBeforeStyles",g,m?.headBeforeStyles),headBeforeScripts:getHtmlPluginConfig("headBeforeScripts",g,m?.headBeforeScripts),bodyAfterScripts:getHtmlPluginConfig("bodyAfterScripts",g,m?.bodyAfterScripts),viewport:allowPx2remForModule(i,t)?getHtmlPluginConfig("viewport",g,m?.viewport):""},multiCdn:{env:e,disabled:(m?.excludeCdnEnvs||["me","dev","ntv"]).includes(e)}}));return r};
1
+ import HtmlWebpackPlugin from 'html-webpack-plugin';
2
+ import { allowPx2remForModule } from '../../helpers/allow-px2rem-for-module.js';
3
+ import { getHtmlPluginConfig, } from '../../helpers/get-html-plugin-config.js';
4
+ import { findEnvCdn } from '../../helpers/script-injects.js';
5
+ const minifyOpts = {
6
+ minifyJS: true,
7
+ removeComments: true,
8
+ collapseWhitespace: true,
9
+ collapseBooleanAttributes: false,
10
+ };
11
+ /**
12
+ * Create `html-webpack-plugin` for this build, refer to best practices
13
+ * We'd better pass only one entry for each `build` cycle
14
+ * @param buildEntryItem the entries for this `build`
15
+ * @param allEnv
16
+ */
17
+ export const createMultiHtmlWebpackPlugin = (serveMode, evolveOptions, entryMapItem, allEnv) => {
18
+ const [entryKey, entryConfig] = entryMapItem;
19
+ const htmlPlugins = [];
20
+ const { options } = entryConfig;
21
+ const mode = serveMode ? 'development' : 'production';
22
+ for (const env of allEnv) {
23
+ const envCdn = findEnvCdn(evolveOptions.multiHtmlCdn, env);
24
+ const configData = {
25
+ mode,
26
+ envCdn,
27
+ };
28
+ htmlPlugins.push(new HtmlWebpackPlugin({
29
+ inject: 'body',
30
+ title: getHtmlPluginConfig('title', configData, options?.title),
31
+ chunks: [entryKey],
32
+ // `minify` is true, `dev` always don't minify.
33
+ minify: options?.htmlMinify === false || ['me', 'dev'].includes(env)
34
+ ? false
35
+ : minifyOpts,
36
+ // output file path
37
+ filename: `${entryKey}/index${env === 'prod' ? '' : `-${env}`}.html`,
38
+ // html template
39
+ template: getHtmlPluginConfig('templatePath', configData, options?.templatePath).replace(`{0}`, env),
40
+ // template parameters
41
+ templateParameters: {
42
+ // The page title
43
+ title: getHtmlPluginConfig('title', configData, options?.title),
44
+ // The page favicon
45
+ favicon: getHtmlPluginConfig('favicon', configData, options?.favicon),
46
+ // The customized inject head tags.
47
+ headBeforeHtmlTags: getHtmlPluginConfig('headBeforeHtmlTags', configData, options?.headBeforeHtmlTags),
48
+ // The customized inject inline scripts.
49
+ inlineScripts: getHtmlPluginConfig('inlineScripts', configData, options?.inlineScripts),
50
+ // The ordered styles will be injected start of html head.
51
+ headBeforeStyles: getHtmlPluginConfig('headBeforeStyles', configData, options?.headBeforeStyles),
52
+ // The ordered scripts will be injected before html head.
53
+ headBeforeScripts: getHtmlPluginConfig('headBeforeScripts', configData, options?.headBeforeScripts),
54
+ // The ordered scripts will be injected end of html body.
55
+ bodyAfterScripts: getHtmlPluginConfig('bodyAfterScripts', configData, options?.bodyAfterScripts),
56
+ // `allowPx2rem` default is true
57
+ viewport: allowPx2remForModule(entryMapItem, evolveOptions)
58
+ ? getHtmlPluginConfig('viewport', configData, options?.viewport)
59
+ : '',
60
+ },
61
+ // Some options for plugin used the `hook` of `html-webpack-plugin`
62
+ multiCdn: {
63
+ env,
64
+ // use relative path for `me`, `dev`, `ntv`
65
+ disabled: (options?.excludeCdnEnvs || ['me', 'dev', 'ntv']).includes(env),
66
+ },
67
+ }));
68
+ }
69
+ return htmlPlugins;
70
+ };
@@ -1 +1,8 @@
1
- export*from"./types-dev-server.js";export*from"./types-entry-map.js";export*from"./types-federation.js";export*from"./types-modular-import.js";export*from"./types-multi-html.js";export*from"./types-options.js";export*from"./types-loader-options.js";export*from"./types-webpack.js";
1
+ export * from './types-dev-server.js';
2
+ export * from './types-entry-map.js';
3
+ export * from './types-federation.js';
4
+ export * from './types-modular-import.js';
5
+ export * from './types-multi-html.js';
6
+ export * from './types-options.js';
7
+ export * from './types-loader-options.js';
8
+ export * from './types-webpack.js';
@@ -1 +1 @@
1
- export{};
1
+ export {};