UNPKG

@backstage/cli

Version:

CLI for developing Backstage plugins and apps

607 lines (597 loc) • 18.8 kB
'use strict'; 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