-
Notifications
You must be signed in to change notification settings - Fork 17
/
webpack.config.cjs
236 lines (223 loc) · 9.39 KB
/
webpack.config.cjs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
// (C) 2007-2021 GoodData Corporation
const HtmlWebpackPlugin = require("html-webpack-plugin");
const CaseSensitivePathsPlugin = require("case-sensitive-paths-webpack-plugin");
const { ModuleFederationPlugin } = require("webpack").container;
const { DefinePlugin, EnvironmentPlugin } = require("webpack");
const path = require("path");
const { URL } = require("url");
const deps = require("./package.json").dependencies;
const peerDeps = require("./package.json").peerDependencies;
const Dotenv = require("dotenv-webpack");
require("dotenv").config();
const { MODULE_FEDERATION_NAME } = require("./src/metadata.json");
const PORT = process.env.PORT || 3001;
const DEFAULT_BACKEND_URL = "https://live-examples-proxy.herokuapp.com";
const proxyEndpoint = process.env.BACKEND_TYPE === "bear" ? "/gdc" : "/api";
function generateGooddataSharePackagesEntries() {
// add all the gooddata packages that absolutely need to be shared and singletons because of contexts
// allow sharing @gooddata/sdk-ui-dashboard here so that multiple plugins can share it among themselves
// this makes redux related contexts work for example
return [...Object.entries(deps), ...Object.entries(peerDeps)]
.filter(([pkgName]) => pkgName.startsWith("@gooddata"))
.reduce((acc, [pkgName, _version]) => {
acc[pkgName] = {
singleton: true,
requiredVersion: false
};
return acc;
}, {});
}
module.exports = (_env, argv) => {
const isProduction = argv.mode === "production";
const effectiveBackendUrl = process.env.BACKEND_URL || DEFAULT_BACKEND_URL;
const protocol = new URL(effectiveBackendUrl).protocol;
const proxy = {
[proxyEndpoint]: {
changeOrigin: true,
cookieDomainRewrite: "127.0.0.1",
secure: false,
target: effectiveBackendUrl,
headers: {
host: effectiveBackendUrl.replace(/^https?:\/\//, ""),
// This is essential for Tiger backends. To ensure 401 flies when not authenticated and using proxy
"X-Requested-With": "XMLHttpRequest",
},
onProxyReq(proxyReq) {
// changeOrigin: true does not work well for POST requests, so remove origin like this to be safe
proxyReq.removeHeader("origin");
proxyReq.setHeader("accept-encoding", "identity");
},
},
};
const commonConfig = {
mode: isProduction ? "production" : "development",
target: "web",
devtool: isProduction ? false : "eval-cheap-module-source-map",
experiments: {
outputModule: true,
},
output: {
path: path.resolve("./esm"),
filename: "[name].mjs",
library: {
type: "module",
},
},
resolve: {
// Alias for ESM imports with .js suffix because
// `import { abc } from "../abc.js"` may be in fact importing from `abc.tsx` file
extensionAlias: {
".js": [".ts", ".tsx", ".js", ".jsx"],
},
// Prefer ESM versions of packages to enable tree shaking
mainFields: ["module", "browser", "main"],
fallback: {
// semver package depends on node `util`,
// but node API is no longer supported with webpack >= 5
util: false,
},
},
module: {
rules: [
// TS source files in case TS is used
{
test: /\.tsx?$/,
use: [
{
loader: "babel-loader",
},
{
loader: "ts-loader",
options: {
transpileOnly: true,
},
},
],
},
// JS source files in case JS is used
{
test: /\.jsx?$/,
exclude: /node_modules/,
use: [
{
loader: "babel-loader",
options: {
presets: ["@babel/preset-react"],
},
},
],
},
{
test: /\.css$/,
use: ["style-loader", "css-loader"],
},
{
test: /\.(eot|woff|ttf|svg|jpg|jpeg|gif)/,
type: "asset/resource",
},
!isProduction && {
test: /\.js$/,
enforce: "pre",
include: path.resolve(__dirname, "src"),
use: ["source-map-loader"],
},
].filter(Boolean),
},
plugins: [
new CaseSensitivePathsPlugin(),
// provide bogus process.env keys that lru-cache, pseudomap and util packages use unsafely for no reason...
new EnvironmentPlugin({
npm_package_name: "",
npm_lifecycle_script: "",
_nodeLRUCacheForceNoSymbol: "",
TEST_PSEUDOMAP: "",
NODE_DEBUG: "",
}),
],
};
return [
{
...commonConfig,
entry: "./src/index.js",
experiments: { ...commonConfig.experiments, topLevelAwait: true },
name: "harness",
ignoreWarnings: [/Failed to parse source map/], // some of the dependencies have invalid source maps, we do not care that much
devServer: {
static: {
directory: path.join(__dirname, "esm"),
},
port: PORT,
host: "127.0.0.1",
proxy,
https: protocol === "https:",
},
plugins: [
...commonConfig.plugins,
new DefinePlugin({
PORT: JSON.stringify(PORT),
}),
new Dotenv({
silent: true,
systemvars: true,
}),
new Dotenv({
path: ".env.secrets",
silent: true,
systemvars: true,
}),
new HtmlWebpackPlugin({
template: "./src/harness/public/index.html",
scriptLoading: "module",
}),
],
},
{
...commonConfig,
entry: `./src/${MODULE_FEDERATION_NAME}/index.js`,
name: "dashboardPlugin",
output: { ...commonConfig.output, path: path.join(__dirname, "esm", "dashboardPlugin") },
plugins: [
...commonConfig.plugins,
new ModuleFederationPlugin({
library: { type: "window", name: MODULE_FEDERATION_NAME },
name: MODULE_FEDERATION_NAME, // this is used to put the plugin on the target window scope by default
exposes: {
/**
* this is the main entry point providing info about the engine and plugin
* this allows us to only load the plugin and/or engine when needed
*/
[`./${MODULE_FEDERATION_NAME}_ENTRY`]: `./src/${MODULE_FEDERATION_NAME}_entry/index.js`,
/**
* this is the entry to the plugin itself
*/
[`./${MODULE_FEDERATION_NAME}_PLUGIN`]: `./src/${MODULE_FEDERATION_NAME}/index.js`,
/**
* this is the entry to the engine
*/
[`./${MODULE_FEDERATION_NAME}_ENGINE`]: `./src/${MODULE_FEDERATION_NAME}_engine/index.js`,
},
// adds react as shared module
// version is inferred from package.json
// there is no version check for the required version
// so it will always use the higher version found
shared: {
react: {
import: "react", // the "react" package will be used a provided and fallback module
shareKey: "react", // under this name the shared module will be placed in the share scope
singleton: true, // only a single version of the shared module is allowed
requiredVersion: deps.react,
},
"react-dom": {
singleton: true,
requiredVersion: deps["react-dom"],
},
// add all the packages that absolutely need to be shared and singletons because of contexts
// change the allowPrereleaseVersions to true if you want to work with alpha or beta versions
// beware that alpha and beta versions may break and may contain bugs, use at your own risk
...generateGooddataSharePackagesEntries(),
},
}),
],
},
];
};