1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Add package for shared webpack configurations for Lens applications, Features and libraries

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>
This commit is contained in:
Janne Savolainen 2023-01-30 07:54:56 +02:00
parent dfc9b2b981
commit 9c7e6689b6
No known key found for this signature in database
GPG Key ID: 8C6CFB2FFFE8F68A
10 changed files with 701 additions and 0 deletions

View File

@ -0,0 +1,76 @@
# @k8slens/webpack
This package contains webpack configurations for Lens packages.
## Install
```
$ npm install @k8slens/webpack
```
## Features
### Configurations
### Node package
This configuration should be used when creating package that will be executed within **Node** environment.
**webpack.config.js**
```javascript
module.exports = require("@k8slens/webpack").configForNode;
```
### React package
This configuration should be used when creating package tha will be executed within **Browser** environment.
**webpack.config.js**
```javascript
module.exports = require("@k8slens/webpack").configForReact;
```
### Multi export package
This configuration should be used when package contains **multiple entrypoint** e.g. for different environments. You need to add `lensMultiExportConfig` to `package.json` with configuration. Note that also `exports` property needs to be set, but the correct values are generated from `lensMultiExportConfig` when using `lens-build` -script.
**webpack.config.js**
```javascript
const packageJson = require("./package.json");
module.exports = require("@k8slens/webpack").getMultiExportConfig(packageJson);
```
**package.json**
```json
{
"lensMultiExportConfig": {
"./main": {
"buildType": "node",
"entrypoint": "./src/main/index.ts"
},
"./renderer": {
"buildType": "react",
"entrypoint": "./src/renderer/index.ts"
}
},
"exports": {
"./main": {
"types": "./build/main/index.d.ts",
"require": "./build/main/index.js",
"import": "./build/main/index.js",
"default": "./build/main/index.js"
},
"./renderer": {
"types": "./build/renderer/index.d.ts",
"require": "./build/renderer/index.js",
"import": "./build/renderer/index.js",
"default": "./build/renderer/index.js"
}
}
}
```
## Scripts
1. `lens-build` which builds the packages
2. `lens-remove-build` which removes the build directory from packages. It's useful for cleaning up.

View File

@ -0,0 +1,2 @@
set -e
webpack $@

View File

@ -0,0 +1 @@
rm -rfv build

View File

@ -0,0 +1,5 @@
module.exports = {
configForNode: require("./src/node-config"),
configForReact: require("./src/react-config"),
getMultiExportConfig: require("./src/get-multi-export-config"),
};

View File

@ -0,0 +1,8 @@
const { configForNode } =
require("@k8slens/jest").monorepoPackageConfig(__dirname);
module.exports = {
...configForNode,
collectCoverageFrom: [...configForNode.collectCoverageFrom],
};

View File

@ -0,0 +1,36 @@
{
"name": "@k8slens/webpack",
"private": false,
"version": "0.0.1",
"description": "Webpack configurations and scripts for Lens packages.",
"type": "commonjs",
"repository": {
"type": "git",
"url": "git+https://github.com/lensapp/lens.git"
},
"main": "index.js",
"author": {
"name": "OpenLens Authors",
"email": "info@k8slens.dev"
},
"license": "MIT",
"homepage": "https://github.com/lensapp/lens",
"bin": {
"lens-build": "bin/build.sh",
"lens-remove-build": "bin/remove-build.sh"
},
"scripts": {
"test": "lens-test"
},
"dependencies": {
"@types/webpack-env": "^1.18.0",
"css-loader": "^6.7.2",
"mini-css-extract-plugin": "^2.7.0",
"sass-loader": "^13.2.0",
"style-loader": "^3.3.1",
"ts-loader": "^9.4.1",
"webpack": "^5.75.0",
"webpack-cli": "^4.10.0",
"webpack-node-externals": "^3.0.0"
}
}

View File

@ -0,0 +1,157 @@
const nodeConfig = require("./node-config");
const reactConfig = require("./react-config");
const path = require("path");
const {
map,
isEqual,
keys,
fromPairs,
toPairs,
reject,
values,
nth,
filter,
} = require("lodash/fp");
const { pipeline } = require("@ogre-tools/fp");
module.exports = (
packageJson,
dependencies = { workingDirectory: process.cwd(), nodeConfig, reactConfig }
) => {
if (!packageJson.lensMultiExportConfig) {
throw new Error(
`Tried to get multi export config for package "${packageJson.name}" but configuration is missing.`
);
}
const validBuildTypes = ["node", "react"];
const invalidBuildTypes = pipeline(
packageJson.lensMultiExportConfig,
values,
map((config) => config.buildType),
reject((buildType) => validBuildTypes.includes(buildType))
);
if (invalidBuildTypes.length > 0) {
throw new Error(
`Tried to get multi export config for package "${
packageJson.name
}" but build types "${invalidBuildTypes.join(
'", "'
)}" were not any of "${validBuildTypes.join('", "')}".`
);
}
const exportsWithMissingEntrypoint = pipeline(
packageJson.lensMultiExportConfig,
toPairs,
filter(([, config]) => !config.entrypoint),
map(nth(0))
);
if (exportsWithMissingEntrypoint.length > 0) {
throw new Error(
`Tried to get multi export config for package "${
packageJson.name
}" but entrypoint was missing for "${exportsWithMissingEntrypoint.join(
'", "'
)}".`
);
}
const expectedExports = pipeline(
packageJson.lensMultiExportConfig,
keys,
map(toExpectedExport),
fromPairs
);
if (!isEqual(expectedExports, packageJson.exports)) {
throw new Error(
`Tried to get multi export config but exports of package.json for "${
packageJson.name
}" did not match exactly:\n\n${JSON.stringify(expectedExports, null, 2)}`
);
}
return pipeline(
packageJson.lensMultiExportConfig,
toPairs,
map(toExportSpecificWebpackConfigFor(dependencies))
);
};
const toExpectedExport = (externalImportPath) => {
const entrypointPath = `./${path.join(
"./build",
externalImportPath,
"index.js"
)}`;
return [
externalImportPath,
{
types: `./${path.join("./build", externalImportPath, "index.d.ts")}`,
default: entrypointPath,
import: entrypointPath,
require: entrypointPath,
},
];
};
const getRuleWithExportSpecificDeclarationDir = (rule, outputPath, rootDir) => {
return {
...rule,
options: {
...rule.options,
compilerOptions: {
...rule.options.compilerOptions,
declarationDir: outputPath,
rootDir: rootDir,
},
},
};
};
const toExportSpecificWebpackConfigFor =
(dependencies) =>
([externalImportPath, { buildType, entrypoint }]) => {
const baseConfig =
buildType === "node" ? dependencies.nodeConfig : dependencies.reactConfig;
const outputDirectory = path.join(
dependencies.workingDirectory,
"build",
externalImportPath
);
return {
...baseConfig,
name: entrypoint,
entry: {
index: entrypoint,
},
output: {
...baseConfig.output,
path: outputDirectory,
},
module: {
rules: baseConfig.module.rules.map((rule) =>
rule.loader === "ts-loader"
? getRuleWithExportSpecificDeclarationDir(
rule,
outputDirectory,
path.join(dependencies.workingDirectory, "src")
)
: rule
),
},
};
};

View File

@ -0,0 +1,300 @@
import getMultiExportConfig from "./get-multi-export-config";
describe("get-multi-export-config", () => {
let actual;
let maximalPackageJson;
beforeEach(() => {
maximalPackageJson = {
name: "some-name",
lensMultiExportConfig: {
".": {
buildType: "node",
entrypoint: "./index.ts",
},
"./some-entrypoint": {
buildType: "node",
entrypoint: "./some-entrypoint/index.ts",
},
"./some-other-entrypoint": {
buildType: "react",
entrypoint: "./some-other-entrypoint/index.ts",
},
},
exports: {
".": {
types: "./build/index.d.ts",
require: "./build/index.js",
import: "./build/index.js",
default: "./build/index.js",
},
"./some-entrypoint": {
types: "./build/some-entrypoint/index.d.ts",
require: "./build/some-entrypoint/index.js",
import: "./build/some-entrypoint/index.js",
default: "./build/some-entrypoint/index.js",
},
"./some-other-entrypoint": {
types: "./build/some-other-entrypoint/index.d.ts",
require: "./build/some-other-entrypoint/index.js",
import: "./build/some-other-entrypoint/index.js",
default: "./build/some-other-entrypoint/index.js",
},
},
};
});
it("given maximal package.json, when creating configuration, works", () => {
actual = getMultiExportConfig(maximalPackageJson, {
workingDirectory: "/some-working-directory",
nodeConfig: nodeConfigStub,
reactConfig: reactConfigStub,
});
expect(actual).toEqual([
{
name: "./index.ts",
stub: "node",
entry: { index: "./index.ts" },
output: { some: "value", path: "/some-working-directory/build" },
module: {
rules: [
{
test: "some-test",
loader: "ts-loader",
options: {
compilerOptions: {
declaration: "some-declaration",
declarationDir: "/some-working-directory/build",
rootDir: "/some-working-directory/src",
},
},
},
{
some: "other-rule",
},
],
},
},
{
name: "./some-entrypoint/index.ts",
stub: "node",
entry: { index: "./some-entrypoint/index.ts" },
output: {
some: "value",
path: "/some-working-directory/build/some-entrypoint",
},
module: {
rules: [
{
test: "some-test",
loader: "ts-loader",
options: {
compilerOptions: {
declaration: "some-declaration",
declarationDir:
"/some-working-directory/build/some-entrypoint",
rootDir: "/some-working-directory/src",
},
},
},
{
some: "other-rule",
},
],
},
},
{
name: "./some-other-entrypoint/index.ts",
stub: "react",
entry: { index: "./some-other-entrypoint/index.ts" },
output: {
some: "other-value",
path: "/some-working-directory/build/some-other-entrypoint",
},
module: {
rules: [
{
test: "some-test",
loader: "ts-loader",
options: {
compilerOptions: {
declaration: "some-declaration",
declarationDir:
"/some-working-directory/build/some-other-entrypoint",
rootDir: "/some-working-directory/src",
},
},
},
{
some: "other-rule",
},
],
},
},
]);
});
it("given maximal package.json but path for entrypoint in exports do not match output, when creating configuration, throws", () => {
maximalPackageJson.exports["./some-entrypoint"].default = "wrong-path";
expect(() => {
getMultiExportConfig(maximalPackageJson, {
workingDirectory: "/some-working-directory",
nodeConfig: nodeConfigStub,
reactConfig: reactConfigStub,
});
}).toThrow(
'Tried to get multi export config but exports of package.json for "some-name" did not match exactly:'
);
});
it("given maximal package.json but exports do not match lens multi export config, when creating configuration, throws", () => {
maximalPackageJson.exports = {};
expect(() => {
getMultiExportConfig(maximalPackageJson, {
workingDirectory: "/some-working-directory",
nodeConfig: nodeConfigStub,
reactConfig: reactConfigStub,
});
}).toThrow(
'Tried to get multi export config but exports of package.json for "some-name" did not match exactly:'
);
});
it("given maximal package.json but exports are missing, when creating configuration, throws", () => {
delete maximalPackageJson.exports;
expect(() => {
getMultiExportConfig(maximalPackageJson, {
workingDirectory: "/some-working-directory",
nodeConfig: nodeConfigStub,
reactConfig: reactConfigStub,
});
}).toThrow(
'Tried to get multi export config but exports of package.json for "some-name" did not match exactly:'
);
});
it("given maximal package.json but lens multi export config is missing, when creating configuration, throws", () => {
delete maximalPackageJson.lensMultiExportConfig;
expect(() => {
getMultiExportConfig(maximalPackageJson, {
workingDirectory: "/some-working-directory",
nodeConfig: nodeConfigStub,
reactConfig: reactConfigStub,
});
}).toThrow(
'Tried to get multi export config for package "some-name" but configuration is missing.'
);
});
it("given maximal package.json but a build type is incorrect, when creating configuration, throws", () => {
maximalPackageJson.lensMultiExportConfig["."].buildType = "some-invalid";
expect(() => {
getMultiExportConfig(maximalPackageJson, {
workingDirectory: "/some-working-directory",
nodeConfig: nodeConfigStub,
reactConfig: reactConfigStub,
});
}).toThrow(
'Tried to get multi export config for package "some-name" but build types "some-invalid" were not any of "node", "react".'
);
});
it("given maximal package.json but entrypoint is missing, when creating configuration, throws", () => {
delete maximalPackageJson.lensMultiExportConfig["./some-entrypoint"]
.entrypoint;
expect(() => {
getMultiExportConfig(maximalPackageJson, {
workingDirectory: "/some-working-directory",
nodeConfig: nodeConfigStub,
reactConfig: reactConfigStub,
});
}).toThrow(
'Tried to get multi export config for package "some-name" but entrypoint was missing for "./some-entrypoint".'
);
});
});
const nodeConfigStub = {
stub: "node",
output: {
some: "value",
path: "irrelevant",
},
module: {
rules: [
{
test: "some-test",
loader: "ts-loader",
options: {
compilerOptions: {
declaration: "some-declaration",
declarationDir: "irrelevant",
},
},
},
{
some: "other-rule",
},
],
},
};
const reactConfigStub = {
stub: "react",
output: {
some: "other-value",
path: "irrelevant",
},
module: {
rules: [
{
test: "some-test",
loader: "ts-loader",
options: {
compilerOptions: {
declaration: "some-declaration",
declarationDir: "irrelevant",
},
},
},
{
some: "other-rule",
},
],
},
};

View File

@ -0,0 +1,69 @@
const nodeExternals = require("webpack-node-externals");
const path = require("path");
const buildDirectory = path.resolve(process.cwd(), "build");
module.exports = {
entry: { index: "./index.ts" },
target: "node",
mode: "production",
performance: {
maxEntrypointSize: 100000,
hints: "error",
},
resolve: {
extensions: [".ts", ".tsx"],
},
output: {
path: buildDirectory,
filename: (pathData) =>
pathData.chunk.name === "index"
? "index.js"
: `${pathData.chunk.name}/index.js`,
libraryTarget: "commonjs2",
},
externals: [
nodeExternals({ modulesFromFile: true }),
nodeExternals({
modulesDir: path.resolve(
__dirname,
"..",
"..",
"..",
"..",
"node_modules"
),
}),
],
externalsPresets: { node: true },
node: {
__dirname: true,
__filename: true,
},
module: {
rules: [
{
test: /\.ts(x)?$/,
loader: "ts-loader",
options: {
compilerOptions: {
rootDir: process.cwd(),
declaration: true,
declarationDir: buildDirectory,
},
},
},
],
},
};

View File

@ -0,0 +1,47 @@
const nodeConfig = require("./node-config");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const path = require("path");
module.exports = {
...nodeConfig,
plugins: [
// ...nodeConfig.plugins,
new MiniCssExtractPlugin({
filename: "[name].css",
}),
],
module: {
...nodeConfig.module,
rules: [
...nodeConfig.module.rules,
{
test: /\.s?css$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: "css-loader",
options: {
sourceMap: false,
modules: {
auto: /\.module\./i, // https://github.com/webpack-contrib/css-loader#auto
mode: "local", // :local(.selector) by default
localIdentName: "[name]__[local]--[hash:base64:5]",
},
},
},
{
loader: "sass-loader",
options: {
sourceMap: false,
},
},
],
},
],
},
};