@backstage/cli
Version:
CLI for developing Backstage plugins and apps
607 lines (597 loc) • 18.8 kB
JavaScript
var fs = require('fs-extra');
var path = require('path');
var ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
var runScriptWebpackPlugin = require('run-script-webpack-plugin');
var webpack = require('webpack');
var nodeExternals = require('webpack-node-externals');
var cliCommon = require('@backstage/cli-common');
var getPackages = require('@manypkg/get-packages');
var MiniCssExtractPlugin = require('mini-css-extract-plugin');
var svgrTemplate = require('./svgrTemplate-d1dad6d3.cjs.js');
var ReactRefreshPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
var index = require('./index-d2845aa8.cjs.js');
var run = require('./run-eac5f3ab.cjs.js');
var ESLintPlugin = require('eslint-webpack-plugin');
var pickBy = require('lodash/pickBy');
var yn = require('yn');
var entryPoints = require('./entryPoints-e81a0dba.cjs.js');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
var ForkTsCheckerWebpackPlugin__default = /*#__PURE__*/_interopDefaultLegacy(ForkTsCheckerWebpackPlugin);
var HtmlWebpackPlugin__default = /*#__PURE__*/_interopDefaultLegacy(HtmlWebpackPlugin);
var ModuleScopePlugin__default = /*#__PURE__*/_interopDefaultLegacy(ModuleScopePlugin);
var webpack__default = /*#__PURE__*/_interopDefaultLegacy(webpack);
var nodeExternals__default = /*#__PURE__*/_interopDefaultLegacy(nodeExternals);
var MiniCssExtractPlugin__default = /*#__PURE__*/_interopDefaultLegacy(MiniCssExtractPlugin);
var ReactRefreshPlugin__default = /*#__PURE__*/_interopDefaultLegacy(ReactRefreshPlugin);
var ESLintPlugin__default = /*#__PURE__*/_interopDefaultLegacy(ESLintPlugin);
var pickBy__default = /*#__PURE__*/_interopDefaultLegacy(pickBy);
var yn__default = /*#__PURE__*/_interopDefaultLegacy(yn);
const { ESBuildMinifyPlugin } = require("esbuild-loader");
const optimization = (options) => {
const { isDev } = options;
return {
minimize: !isDev,
minimizer: [
new ESBuildMinifyPlugin({
target: "es2019",
format: "iife"
})
],
runtimeChunk: "single",
splitChunks: {
automaticNameDelimiter: "-",
cacheGroups: {
default: false,
// Put all vendor code needed for initial page load in individual files if they're big
// enough, if they're smaller they end up in the main
packages: {
chunks: "initial",
test(module) {
var _a;
return Boolean(
(_a = module == null ? void 0 : module.resource) == null ? void 0 : _a.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)
);
},
name(module) {
const packageName = module.resource.match(
/[\\/]node_modules[\\/](.*?)([\\/]|$)/
)[1];
return packageName.replace("@", "");
},
filename: isDev ? "module-[name].js" : "static/module-[name].[chunkhash:8].js",
priority: 10,
minSize: 1e5,
minChunks: 1,
maxAsyncRequests: Infinity,
maxInitialRequests: Infinity
},
// filename is not included in type, but we need it
// Group together the smallest modules
vendor: {
chunks: "initial",
test: /[\\/]node_modules[\\/]/,
name: "vendor",
priority: 5,
enforce: true
}
}
}
};
};
const transforms = (options) => {
const { isDev, isBackend } = options;
function insertBeforeJssStyles(element) {
const head = document.head;
const firstJssNode = head.querySelector("style[data-jss]");
if (!firstJssNode) {
head.appendChild(element);
} else {
head.insertBefore(element, firstJssNode);
}
}
const loaders = [
{
test: /\.(tsx?)$/,
exclude: /node_modules/,
use: [
{
loader: require.resolve("swc-loader"),
options: {
jsc: {
target: "es2019",
externalHelpers: !isBackend,
parser: {
syntax: "typescript",
tsx: !isBackend,
dynamicImport: true
},
transform: {
react: isBackend ? void 0 : {
runtime: "automatic",
refresh: isDev
}
}
}
}
}
]
},
{
test: /\.(jsx?|mjs|cjs)$/,
exclude: /node_modules/,
use: [
{
loader: require.resolve("swc-loader"),
options: {
jsc: {
target: "es2019",
externalHelpers: !isBackend,
parser: {
syntax: "ecmascript",
jsx: !isBackend,
dynamicImport: true
},
transform: {
react: isBackend ? void 0 : {
runtime: "automatic",
refresh: isDev
}
}
}
}
}
]
},
{
test: /\.(js|mjs|cjs)$/,
resolve: {
fullySpecified: false
}
},
{
test: [/\.icon\.svg$/],
use: [
{
loader: require.resolve("swc-loader"),
options: {
jsc: {
target: "es2019",
externalHelpers: !isBackend,
parser: {
syntax: "ecmascript",
jsx: !isBackend,
dynamicImport: true
}
}
}
},
{
loader: require.resolve("@svgr/webpack"),
options: { babel: false, template: svgrTemplate.svgrTemplate }
}
]
},
{
test: [
/\.bmp$/,
/\.gif$/,
/\.jpe?g$/,
/\.png$/,
/\.frag$/,
/\.vert$/,
{ and: [/\.svg$/, { not: [/\.icon\.svg$/] }] },
/\.xml$/
],
type: "asset/resource",
generator: {
filename: "static/[name].[hash:8].[ext]"
}
},
{
test: /\.(eot|woff|woff2|ttf)$/i,
type: "asset/resource",
generator: {
filename: "static/[name].[hash][ext][query]"
}
},
{
test: /\.ya?ml$/,
use: require.resolve("yml-loader")
},
{
include: /\.(md)$/,
type: "asset/resource",
generator: {
filename: "static/[name].[hash][ext][query]"
}
},
{
test: /\.css$/i,
use: [
isDev ? {
loader: require.resolve("style-loader"),
options: {
insert: insertBeforeJssStyles
}
} : MiniCssExtractPlugin__default["default"].loader,
{
loader: require.resolve("css-loader"),
options: {
sourceMap: true
}
}
]
}
];
const plugins = new Array();
if (isDev) {
if (!isBackend) {
plugins.push(
new ReactRefreshPlugin__default["default"]({
overlay: { sockProtocol: "ws" }
})
);
}
} else {
plugins.push(
new MiniCssExtractPlugin__default["default"]({
filename: "static/[name].[contenthash:8].css",
chunkFilename: "static/[name].[id].[contenthash:8].css",
insert: insertBeforeJssStyles
// Only applies to async chunks
})
);
}
return { loaders, plugins };
};
class LinkedPackageResolvePlugin {
constructor(targetModules, packages) {
this.targetModules = targetModules;
this.packages = packages;
}
apply(resolver) {
resolver.hooks.resolve.tapAsync(
"LinkedPackageResolvePlugin",
(data, context, callback) => {
var _a;
const pkg = this.packages.find(
(pkge) => data.path && cliCommon.isChildPath(pkge.dir, data.path)
);
if (!pkg) {
callback();
return;
}
const modulesLocation = path.resolve(
this.targetModules,
pkg.packageJson.name
);
const newContext = ((_a = data.context) == null ? void 0 : _a.issuer) ? {
...data.context,
issuer: data.context.issuer.replace(pkg.dir, modulesLocation)
} : data.context;
resolver.doResolve(
resolver.hooks.resolve,
{
...data,
context: newContext,
path: data.path && data.path.replace(pkg.dir, modulesLocation)
},
`resolve ${data.request} in ${modulesLocation}`,
context,
callback
);
}
);
}
}
const BUILD_CACHE_ENV_VAR = "BACKSTAGE_CLI_EXPERIMENTAL_BUILD_CACHE";
function resolveBaseUrl(config) {
const baseUrl = config.getString("app.baseUrl");
try {
return new URL(baseUrl);
} catch (error) {
throw new Error(`Invalid app.baseUrl, ${error}`);
}
}
async function readBuildInfo() {
const timestamp = Date.now();
let commit = "unknown";
try {
commit = await run.runPlain("git", "rev-parse", "HEAD");
} catch (error) {
console.warn(`WARNING: Failed to read git commit, ${error}`);
}
let gitVersion = "unknown";
try {
gitVersion = await run.runPlain("git", "describe", "--always");
} catch (error) {
console.warn(`WARNING: Failed to describe git version, ${error}`);
}
const { version: packageVersion } = await fs__default["default"].readJson(
index.paths.resolveTarget("package.json")
);
return {
cliVersion: index.version,
gitVersion,
packageVersion,
timestamp,
commit
};
}
async function createConfig(paths, options) {
const { checksEnabled, isDev, frontendConfig } = options;
const { plugins, loaders } = transforms(options);
const { packages } = await getPackages.getPackages(index.paths.targetDir);
const externalPkgs = packages.filter((p) => !cliCommon.isChildPath(paths.root, p.dir));
const baseUrl = frontendConfig.getString("app.baseUrl");
const validBaseUrl = new URL(baseUrl);
const publicPath = validBaseUrl.pathname.replace(/\/$/, "");
if (checksEnabled) {
plugins.push(
new ForkTsCheckerWebpackPlugin__default["default"]({
typescript: { configFile: paths.targetTsConfig, memoryLimit: 4096 }
}),
new ESLintPlugin__default["default"]({
context: paths.targetPath,
files: ["**/*.(ts|tsx|mts|cts|js|jsx|mjs|cjs)"]
})
);
}
plugins.push(
new webpack.ProvidePlugin({
process: "process/browser",
Buffer: ["buffer", "Buffer"]
})
);
plugins.push(
new webpack__default["default"].EnvironmentPlugin({
APP_CONFIG: options.frontendAppConfigs
})
);
plugins.push(
new HtmlWebpackPlugin__default["default"]({
template: paths.targetHtml,
templateParameters: {
publicPath,
config: frontendConfig
}
})
);
const buildInfo = await readBuildInfo();
plugins.push(
new webpack__default["default"].DefinePlugin({
"process.env.BUILD_INFO": JSON.stringify(buildInfo)
})
);
const reactRefreshFiles = [
require.resolve(
"@pmmmwh/react-refresh-webpack-plugin/lib/runtime/RefreshUtils.js"
),
require.resolve("@pmmmwh/react-refresh-webpack-plugin/overlay/index.js"),
require.resolve("react-refresh")
];
const withCache = yn__default["default"](process.env[BUILD_CACHE_ENV_VAR], { default: false });
return {
mode: isDev ? "development" : "production",
profile: false,
optimization: optimization(options),
bail: false,
performance: {
hints: false
// we check the gzip size instead
},
devtool: isDev ? "eval-cheap-module-source-map" : "source-map",
context: paths.targetPath,
entry: [paths.targetEntry],
resolve: {
extensions: [".ts", ".tsx", ".mjs", ".js", ".jsx", ".json", ".wasm"],
mainFields: ["browser", "module", "main"],
fallback: {
...pickBy__default["default"](require("node-libs-browser")),
module: false,
dgram: false,
dns: false,
fs: false,
http2: false,
net: false,
tls: false,
child_process: false,
/* new ignores */
path: false,
https: false,
http: false,
util: require.resolve("util/")
},
plugins: [
new LinkedPackageResolvePlugin(paths.rootNodeModules, externalPkgs),
new ModuleScopePlugin__default["default"](
[paths.targetSrc, paths.targetDev],
[paths.targetPackageJson, ...reactRefreshFiles]
)
]
},
module: {
rules: loaders
},
output: {
path: paths.targetDist,
publicPath: `${publicPath}/`,
filename: isDev ? "[name].js" : "static/[name].[fullhash:8].js",
chunkFilename: isDev ? "[name].chunk.js" : "static/[name].[chunkhash:8].chunk.js",
...isDev ? {
devtoolModuleFilenameTemplate: (info) => `file:///${path.resolve(info.absoluteResourcePath).replace(
/\\/g,
"/"
)}`
} : {}
},
plugins,
...withCache ? {
cache: {
type: "filesystem",
buildDependencies: {
config: [__filename]
}
}
} : {}
};
}
async function createBackendConfig(paths, options) {
const { checksEnabled, isDev } = options;
const { packages } = await getPackages.getPackages(index.paths.targetDir);
const localPackageEntryPoints = packages.flatMap((p) => {
const entryPoints$1 = entryPoints.readEntryPoints(p.packageJson);
return entryPoints$1.map((e) => path.posix.join(p.packageJson.name, e.mount));
});
const moduleDirs = packages.map((p) => path.resolve(p.dir, "node_modules"));
const externalPkgs = packages.filter((p) => !cliCommon.isChildPath(paths.root, p.dir));
const { loaders } = transforms({ ...options, isBackend: true });
const runScriptNodeArgs = new Array();
if (options.inspectEnabled) {
runScriptNodeArgs.push("--inspect");
} else if (options.inspectBrkEnabled) {
runScriptNodeArgs.push("--inspect-brk");
}
return {
mode: isDev ? "development" : "production",
profile: false,
...isDev ? {
watch: true,
watchOptions: {
ignored: /node_modules\/(?!\@backstage)/
}
} : {},
externals: [
nodeExternalsWithResolve({
modulesDir: paths.rootNodeModules,
additionalModuleDirs: moduleDirs,
allowlist: ["webpack/hot/poll?100", ...localPackageEntryPoints]
})
],
target: "node",
node: {
/* eslint-disable-next-line no-restricted-syntax */
__dirname: true,
__filename: true,
global: true
},
bail: false,
performance: {
hints: false
// we check the gzip size instead
},
devtool: isDev ? "eval-cheap-module-source-map" : "source-map",
context: paths.targetPath,
entry: [
"webpack/hot/poll?100",
paths.targetRunFile ? paths.targetRunFile : paths.targetEntry
],
resolve: {
extensions: [".ts", ".mjs", ".js", ".json"],
mainFields: ["main"],
modules: [paths.rootNodeModules, ...moduleDirs],
plugins: [
new LinkedPackageResolvePlugin(paths.rootNodeModules, externalPkgs),
new ModuleScopePlugin__default["default"](
[paths.targetSrc, paths.targetDev],
[paths.targetPackageJson]
)
]
},
module: {
rules: loaders
},
output: {
path: paths.targetDist,
filename: isDev ? "[name].js" : "[name].[hash:8].js",
chunkFilename: isDev ? "[name].chunk.js" : "[name].[chunkhash:8].chunk.js",
...isDev ? {
devtoolModuleFilenameTemplate: (info) => `file:///${path.resolve(info.absoluteResourcePath).replace(
/\\/g,
"/"
)}`
} : {}
},
plugins: [
new runScriptWebpackPlugin.RunScriptWebpackPlugin({
name: "main.js",
nodeArgs: runScriptNodeArgs.length > 0 ? runScriptNodeArgs : void 0,
args: process.argv.slice(3)
// drop `node backstage-cli backend:dev`
}),
new webpack__default["default"].HotModuleReplacementPlugin(),
...checksEnabled ? [
new ForkTsCheckerWebpackPlugin__default["default"]({
typescript: { configFile: paths.targetTsConfig }
}),
new ESLintPlugin__default["default"]({
files: ["**/*.(ts|tsx|mts|cts|js|jsx|mjs|cjs)"]
})
] : []
]
};
}
function nodeExternalsWithResolve(options) {
let currentContext;
const externals = nodeExternals__default["default"]({
...options,
importType(request) {
const resolved = require.resolve(request, {
paths: [currentContext]
});
return `commonjs ${resolved}`;
}
});
return ({ context, request }, callback) => {
currentContext = context;
return externals(context, request, callback);
};
}
function resolveBundlingPaths(options) {
const { entry, targetDir = index.paths.targetDir } = options;
const resolveTargetModule = (pathString) => {
for (const ext of ["mjs", "js", "ts", "tsx", "jsx"]) {
const filePath = path.resolve(targetDir, `${pathString}.${ext}`);
if (fs__default["default"].pathExistsSync(filePath)) {
return filePath;
}
}
return path.resolve(targetDir, `${pathString}.js`);
};
let targetPublic = void 0;
let targetHtml = path.resolve(targetDir, "public/index.html");
if (fs__default["default"].pathExistsSync(targetHtml)) {
targetPublic = path.resolve(targetDir, "public");
} else {
targetHtml = path.resolve(targetDir, `${entry}.html`);
if (!fs__default["default"].pathExistsSync(targetHtml)) {
targetHtml = index.paths.resolveOwn("templates/serve_index.html");
}
}
const targetRunFile = path.resolve(targetDir, "src/run.ts");
const runFileExists = fs__default["default"].pathExistsSync(targetRunFile);
return {
targetHtml,
targetPublic,
targetPath: path.resolve(targetDir, "."),
targetRunFile: runFileExists ? targetRunFile : void 0,
targetDist: path.resolve(targetDir, "dist"),
targetAssets: path.resolve(targetDir, "assets"),
targetSrc: path.resolve(targetDir, "src"),
targetDev: path.resolve(targetDir, "dev"),
targetEntry: resolveTargetModule(entry),
targetTsConfig: index.paths.resolveTargetRoot("tsconfig.json"),
targetPackageJson: path.resolve(targetDir, "package.json"),
rootNodeModules: index.paths.resolveTargetRoot("node_modules"),
root: index.paths.targetRoot
};
}
exports.createBackendConfig = createBackendConfig;
exports.createConfig = createConfig;
exports.resolveBaseUrl = resolveBaseUrl;
exports.resolveBundlingPaths = resolveBundlingPaths;
//# sourceMappingURL=paths-a7d52d4f.cjs.js.map
;