repacked
Version:
To initialize a new project with `repacked`, you can use the following command:
681 lines (654 loc) • 19.8 kB
JavaScript
// src/utils/cwd.ts
import path from "path";
var cwd = (...paths) => {
return path.join(process.cwd(), ...paths);
};
var cwd_default = cwd;
// src/features/test/jestConfig.ts
import path2 from "path";
// src/features/swc/swcOptions.ts
var getSwcOptions = (options) => {
return {
jsc: {
parser: {
syntax: "typescript",
tsx: options.tsx
},
externalHelpers: false,
transform: {
react: {
runtime: "automatic",
development: !options.isProduction,
refresh: !options.isProduction && !options.isServer
}
}
},
env: {
targets: "Chrome >= 48"
}
};
};
// src/utils/dirname.ts
import { fileURLToPath } from "url";
import { dirname } from "path";
var __filename = fileURLToPath(import.meta.url);
var __dirname = dirname(__filename);
// src/features/test/jestConfig.ts
var getJestConfig = () => {
return {
rootDir: cwd_default(""),
roots: ["<rootDir>/src"],
collectCoverageFrom: ["src/**/*.{js,jsx,ts,tsx}", "!src/**/*.d.ts"],
setupFilesAfterEnv: ["@testing-library/jest-dom"],
testMatch: [
"<rootDir>/src/**/__tests__/**/*.{js,jsx,ts,tsx}",
"<rootDir>/src/**/*.{spec,test}.{js,jsx,ts,tsx}"
],
testEnvironment: "jsdom",
moduleFileExtensions: ["ts", "tsx", "js", "jsx"],
transform: {
"^.+\\.(js|ts|mjs|cjs)$": "@swc/jest",
"^.+\\.(jsx|tsx)$": [
"@swc/jest",
getSwcOptions({ isProduction: true, tsx: true })
],
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": path2.resolve(
__dirname,
"./features/test/transformers/fileTransformer.js"
),
"^.+\\.css$": path2.resolve(
__dirname,
"./features/test/transformers/cssTransformer.js"
)
}
};
};
var getJestConfigAsJSON = (override) => {
return JSON.stringify(override(getJestConfig()));
};
// src/features/test/test.ts
import jest from "jest";
var runTest = async (argv, appConfig) => {
const jestConfig = getJestConfigAsJSON(appConfig.jest);
argv.push("--config", jestConfig);
jest.run(argv);
};
// src/features/serve/index.ts
import { rspack as rspack2 } from "@rspack/core";
import { RspackDevServer } from "@rspack/dev-server";
// src/features/server/server.ts
import express from "express";
var expressServer = () => {
const app = express();
return app;
};
// src/features/serve/index.ts
import history from "connect-history-api-fallback";
import rspackHotMiddleware from "webpack-hot-middleware";
import rspackDevMiddleware from "webpack-dev-middleware";
import { createProxyMiddleware } from "http-proxy-middleware";
// src/features/server/getServerRspackConfig.ts
import { LicenseWebpackPlugin } from "license-webpack-plugin";
// src/features/rspack/getRspackConfig.ts
import "@rspack/dev-server";
var getRspackConfig = async (mode, appConfig, options) => {
const isDevelopment = mode === "development";
const isServer = options?.target === "server";
const configOverride = options?.override ?? ((config) => config);
const outputDirectory = cwd_default(appConfig.output.dir);
const rspackConfig = {
mode,
watch: options?.watch,
watchOptions: options?.watch ? {
ignored: /node_modules/,
poll: 1e3,
aggregateTimeout: 300
} : void 0,
cache: false,
entry: cwd_default(appConfig.client.entry),
devtool: "source-map",
output: {
uniqueName: appConfig.appName,
publicPath: appConfig.client.publicPath,
path: outputDirectory,
filename: "js/[name].[fullhash].js",
clean: typeof options?.clean === "boolean" ? options.clean : true
},
plugins: [],
module: {
rules: [
{
test: /\.(j|t)s?$/,
use: {
loader: "builtin:swc-loader",
options: getSwcOptions({
tsx: false,
isProduction: !isDevelopment,
isServer
})
},
exclude: /node_modules/
},
{
test: /\.(js|ts)x?$/,
use: {
loader: "builtin:swc-loader",
options: getSwcOptions({
tsx: true,
isProduction: !isDevelopment,
isServer
})
},
exclude: /node_modules/
},
{
test: /\.css$/,
exclude: /node_modules/,
use: ["style-loader", "css-loader"]
},
{
test: /\.(png|svg|jpg|gif)$/,
exclude: /node_modules/,
use: [
isServer ? {
loader: "file-loader",
options: {
emitFile: false,
publicPath: appConfig.client.publicPath
}
} : "file-loader"
]
}
]
},
resolve: {
extensions: ["*", ".jsx", ".tsx", ".ts", ".js"]
},
performance: {
hints: false
}
};
appConfig.plugins.forEach((plugin) => {
const customPlugin = plugin({ target: options.target, appConfig });
rspackConfig.plugins?.push(customPlugin);
customPlugin.updateConfig?.(rspackConfig);
});
return appConfig.rspack(
configOverride(rspackConfig),
options?.target ?? "client"
);
};
var getRspackConfig_default = getRspackConfig;
// src/features/server/plugins/hotReloadServer.ts
import cluster from "node:cluster";
import path3 from "path";
var HotReloadServer = class _HotReloadServer {
constructor(appConfig) {
this.appConfig = appConfig;
this.callback = () => {
};
this.workers = [];
cluster.setupPrimary({
exec: path3.resolve(cwd_default(appConfig.output.dir, "index.cjs")),
args: [`--port=${appConfig.development.port + 1}`]
});
cluster.on("online", (worker) => {
this.workers.push(worker);
this.callback();
});
}
apply(compiler) {
const pluginName = _HotReloadServer.name;
compiler.hooks.afterEmit.tapAsync(pluginName, (compilation, callback) => {
this.callback = callback;
this.workers.forEach((worker) => {
try {
worker.process.kill("SIGTERM");
} catch (e) {
console.warn(`Unable to kill worker ${worker.process.pid}`);
}
});
this.workers = [];
cluster.fork();
});
}
};
// src/features/server/getServerRspackConfig.ts
import path4 from "path";
import { DefinePlugin } from "@rspack/core";
var getServerRspackConfig = async (mode, appConfig, options) => {
const runtimeEnv = mode === "production" ? "prod" : "dev";
return await getRspackConfig_default(mode, appConfig, {
...options ?? {},
target: "server",
override: (config) => {
config.module?.rules?.push({
test: path4.join(
__dirname,
"/features/server/runtimes/",
`runtime.${runtimeEnv}.js`
),
use: [
{
loader: path4.resolve(
__dirname,
"./features/server/loaders/loadServer.js"
),
options: {
entry: cwd_default(appConfig.server.entry)
}
}
]
});
config.plugins?.push(
new DefinePlugin({
"process.env.__INTERNAL_REPACKED_SERVER_CONFIG": {
client: {
enabled: appConfig.client.enabled
},
development: {
port: appConfig.development.port
}
}
})
);
if (mode === "production") {
config.plugins?.push(
new LicenseWebpackPlugin({
outputFilename: "LICENSE",
perChunkOutput: false,
addBanner: true,
stats: {
warnings: false
}
})
);
}
if (mode === "development") {
config.plugins?.push(new HotReloadServer(appConfig));
}
config.target = "node";
config.entry = {
index: path4.join(
__dirname,
"/features/server/runtimes/",
`runtime.${runtimeEnv}.js`
)
};
config.output.libraryTarget = "commonjs2";
config.output.filename = "[name].cjs";
config.output.publicPath = appConfig.client.publicPath;
config.optimization = {
sideEffects: true
};
delete config.devServer;
return config;
}
});
};
var getServerRspackConfig_default = getServerRspackConfig;
// src/features/rspack/utils.ts
var filterKnownWarnings = (warnings) => {
const knownPackages = ["yargs", "express", "repacked"].map(
(pkg) => `node_modules/${pkg}`
);
const containsAny = (str, values) => {
return values.some((value) => str.includes(value));
};
return warnings.filter((warning) => {
if (warning.message.includes("Critical dependency") && (!warning.moduleName || containsAny(warning.moduleName ?? "", knownPackages))) {
return false;
}
return true;
});
};
var logRspackErrors = (err, stats) => {
if (err) {
console.error(err.stack || err);
if (err.details) {
console.error(err.details);
}
process.exit(1);
}
const statsData = stats?.toJson();
if (stats?.hasErrors()) {
console.error(statsData?.errors);
process.exit(1);
}
if (stats?.hasWarnings()) {
const filteredWarnings = filterKnownWarnings(statsData?.warnings ?? []);
if (filteredWarnings.length > 0) {
console.warn(filteredWarnings);
}
}
};
// src/features/client/getClientRspackConfig.ts
import "@rspack/dev-server";
import {
HotModuleReplacementPlugin,
HtmlRspackPlugin as HtmlRspackPlugin2,
rspack
} from "@rspack/core";
import ReactRefreshRspackPlugin from "@rspack/plugin-react-refresh";
import { ModuleFederationPlugin } from "@module-federation/enhanced/rspack";
// src/features/client/plugins/envVariables.ts
import { DefinePlugin as DefinePlugin2 } from "@rspack/core";
var getEnvValues = (filterCallback) => {
const envs = process.env || {};
const filteredEnvs = {};
Object.entries(envs).forEach(([key, value]) => {
if (filterCallback(key, value)) {
filteredEnvs[key] = value;
}
});
return filteredEnvs;
};
var EnvVariablesPlugin = (filterCallback) => {
return new DefinePlugin2({
"process.env": JSON.stringify({
...getEnvValues(filterCallback)
})
});
};
// src/features/client/plugins/htmlMFWebpackPlugin.ts
import { HtmlRspackPlugin } from "@rspack/core";
var HtmlMFRspackPlugin = class {
constructor(entryFile = "remoteEntry.js") {
this.entryFile = entryFile;
}
apply(compiler) {
compiler.hooks.compilation.tap(
"HtmlMFRspackPlugin",
(compilation) => {
HtmlRspackPlugin.getCompilationHooks(
compilation
).alterAssetTags.tapAsync("HtmlMFRspackPlugin", (data, cb) => {
const entryFile = `${data.publicPath.toLowerCase() === "auto" ? "" : data.publicPath}${this.entryFile}`;
data.assetTags.scripts = data.assetTags.scripts.filter(
(script) => script.attributes.src !== entryFile
);
cb(null, data);
});
}
);
}
};
var htmlMFWebpackPlugin_default = HtmlMFRspackPlugin;
// src/features/client/plugins/manifestPlugin.ts
import { sources } from "@rspack/core";
var defaultOptions = {
fileName: "client-assets.json"
};
var ManifestPlugin = class _ManifestPlugin {
constructor(options) {
this.options = {
...defaultOptions,
...options
};
}
apply(compiler) {
compiler.hooks.emit.tapAsync(
_ManifestPlugin.name,
(compilation, callback) => {
const manifest = {};
for (const [entryName, entrypoint] of compilation.entrypoints) {
const chunks = entrypoint.chunks.filter(
(chunk) => chunk.canBeInitial?.()
);
const jsFiles = /* @__PURE__ */ new Set();
const cssFiles = /* @__PURE__ */ new Set();
for (const chunk of chunks) {
for (const file of chunk.files) {
if (file.endsWith(".hot-update.js")) {
continue;
}
if (file.endsWith(".js")) {
jsFiles.add(file);
} else if (file.endsWith(".css")) {
cssFiles.add(file);
}
}
}
manifest[entryName] = {
js: Array.from(jsFiles),
css: Array.from(cssFiles)
};
}
const json = JSON.stringify(manifest, null, 2);
compilation.emitAsset(
this.options.fileName,
new sources.RawSource(json)
);
callback();
}
);
}
};
// src/constants.ts
var CLIENT_MANIFEST_FILENAME = "client-assets.json";
// src/features/client/getClientRspackConfig.ts
var getClientRspackConfig = async (mode, appConfig, options) => {
const isDevelopment = mode === "development";
const outputDirectory = cwd_default(appConfig.output.dir);
let entry = [];
if (appConfig.server.enabled && isDevelopment) {
entry = [
"webpack-hot-middleware/client?reload=true",
cwd_default(appConfig.client.entry)
];
} else {
entry = cwd_default(appConfig.client.entry);
}
const plugins = [];
plugins.push(
new HtmlRspackPlugin2({ template: cwd_default(appConfig.client.template) })
);
plugins.push(EnvVariablesPlugin(appConfig.client.envFilter));
isDevelopment && plugins.push(new HotModuleReplacementPlugin());
isDevelopment && plugins.push(new ReactRefreshRspackPlugin({ library: appConfig.appName }));
plugins.push(
new rspack.CopyRspackPlugin({
patterns: [
{
from: cwd_default(appConfig.client.assetsDir),
to: outputDirectory
}
]
})
);
plugins.push(new ManifestPlugin({ fileName: CLIENT_MANIFEST_FILENAME }));
if (appConfig.moduleFederation) {
plugins.push(new ModuleFederationPlugin(appConfig.moduleFederation));
plugins.push(new htmlMFWebpackPlugin_default(appConfig.moduleFederation.filename));
}
return await getRspackConfig_default(mode, appConfig, {
...options ?? {},
target: "client",
override: (config) => {
config.entry = entry;
config.plugins = [...config.plugins, ...plugins];
config.devServer = {
hot: true,
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
"Access-Control-Allow-Headers": "X-Requested-With, content-type, Authorization"
},
...appConfig.development
};
return config;
}
});
};
var getClientRspackConfig_default = getClientRspackConfig;
// src/features/serve/utils/copyHeaders.ts
function copyHeaders(proxyRes, res) {
if (proxyRes.statusCode) {
res.statusCode = proxyRes.statusCode;
}
if (proxyRes.statusMessage) {
res.statusMessage = proxyRes.statusMessage;
}
if (typeof res.setHeader === "function") {
let keys = Object.keys(proxyRes.headers);
keys = keys.filter(
(key) => !["content-encoding", "transfer-encoding"].includes(key)
);
keys.forEach((key) => {
let value = proxyRes.headers[key];
if (value === void 0) {
return;
}
if (key === "set-cookie") {
value = Array.isArray(value) ? value : [value];
value = value.map((x) => x.replace(/Domain=[^;]+?/i, ""));
}
res.setHeader(key, value);
});
} else {
res.headers = proxyRes.headers;
}
}
// src/features/serve/index.ts
var serveClientOnly = async (mode, appConfig) => {
const rspackConfig = await getClientRspackConfig_default(mode, appConfig);
rspackConfig.devServer.historyApiFallback = true;
const compiler = rspack2(rspackConfig);
const server = new RspackDevServer(rspackConfig.devServer ?? {}, compiler);
const runServer = async () => {
console.log("Starting server...");
await server.start();
};
runServer();
};
var serveServer = async (mode, appConfig) => {
const serverRspackConfig = await getServerRspackConfig_default(mode, appConfig, {
watch: true
});
rspack2(serverRspackConfig, logRspackErrors);
const clientRspackConfig = await getClientRspackConfig_default(mode, appConfig, {});
clientRspackConfig.output.publicPath = appConfig.client.publicPath;
const clientCompiler = rspack2(clientRspackConfig);
const port = appConfig.development.port || 3e3;
const clientEnabled = appConfig.client.enabled;
const app = expressServer();
const nextWeakMap = /* @__PURE__ */ new WeakMap();
const proxy = createProxyMiddleware({
target: `http://localhost:${port + 1}`,
changeOrigin: true,
selfHandleResponse: true,
on: {
proxyRes: (proxyRes, req, res) => {
if (!clientEnabled) {
proxyRes.pipe(res);
return;
}
if (proxyRes.headers?.["x-dev-repacked-route-status"] === "unhandled") {
const next = nextWeakMap.get(req);
return next();
} else {
copyHeaders(proxyRes, res);
proxyRes.pipe(res);
}
}
}
});
app.use((req, res, next) => {
if (req.headers["x-dev-repacked-client-only"]) {
return next();
}
nextWeakMap.set(req, next);
proxy(req, res, next);
});
if (clientEnabled) {
app.use(history());
const devMiddleware = rspackDevMiddleware(
//@todo: fix once rspack dev middleware released
clientCompiler,
{
publicPath: clientRspackConfig.output?.publicPath
}
);
app.use(devMiddleware);
app.use(rspackHotMiddleware(clientCompiler));
}
app.listen(port, () => {
console.log(`Server is running at http://localhost:${port}`);
});
};
var serve = async (mode, appConfig) => {
if (appConfig.server.enabled) {
serveServer(mode, appConfig);
} else {
serveClientOnly(mode, appConfig);
}
};
var serve_default = serve;
// src/features/client/build.ts
import { rspack as rspack3 } from "@rspack/core";
var buildClient = async (mode, appConfig) => {
const rspackConfig = await getClientRspackConfig_default(mode, appConfig);
rspack3(rspackConfig, logRspackErrors);
};
// src/features/server/build.ts
import { rspack as rspack4 } from "@rspack/core";
var buildServer = async (mode, appConfig) => {
const rspackConfig = await getServerRspackConfig_default(mode, appConfig, {
clean: false
});
return new Promise((resolve, reject) => {
rspack4(rspackConfig, (err, stats) => {
logRspackErrors(err, stats);
if (err) {
reject(err);
return;
}
if (stats?.hasErrors()) {
const info = stats.toJson();
reject(
new Error(`Build failed with errors:
${info.errors?.join("\n")}`)
);
return;
}
resolve(true);
});
});
};
// src/features/build/index.ts
import path5 from "path";
// src/features/build/utils/removeFolder.ts
import { promises as fs } from "fs";
var removeFolder = async (folder) => {
try {
await fs.rm(folder, { recursive: true, force: true });
console.log(`Folder "${folder}" removed successfully.`);
} catch (error) {
console.error(`Error removing folder: ${error.message}`);
throw error;
}
};
var removeFolder_default = removeFolder;
// src/features/build/index.ts
var build = async (mode, appConfig) => {
const serverEnabled = appConfig.server.enabled;
const clientEnabled = appConfig.client.enabled;
const clientOutputPath = serverEnabled ? path5.join(appConfig.output.dir, "client") : appConfig.output.dir;
await removeFolder_default(cwd_default(appConfig.output.dir));
clientEnabled && await buildClient(mode, {
...appConfig,
output: {
...appConfig.output,
dir: clientOutputPath
}
});
if (serverEnabled) {
await buildServer(mode, appConfig);
}
};
var build_default = build;
export {
build_default as build,
serve_default as serve,
runTest as test
};