@lynx-js/rspeedy
Version:
A webpack/rspack-based frontend toolchain for Lynx
213 lines (212 loc) • 10.7 kB
JavaScript
import { createRequire } from "node:module";
import node_path from "node:path";
import { logger } from "@rsbuild/core";
import { __webpack_require__ } from "./src_cli_main_ts-node_child_process-node_events-node_fs-node_path-node_process.js";
import { debug } from "./src_cli_main_ts-node_child_process-node_events-node_fs-node_path-node_process.js";
import { isLynx } from "./1~is-lynx.js";
class ProvidePlugin_ProvidePlugin {
definitions;
constructor(definitions){
this.definitions = definitions;
}
apply(compiler) {
const { ProvidePlugin } = compiler.webpack;
new ProvidePlugin(this.definitions).apply(compiler);
}
}
const picocolors = __webpack_require__("../../../node_modules/.pnpm/picocolors@1.1.1/node_modules/picocolors/picocolors.js");
var picocolors_default = /*#__PURE__*/ __webpack_require__.n(picocolors);
function pluginDev(options, server) {
return {
name: 'lynx:rsbuild:dev',
apply (config, { action }) {
return 'dev' === action || 'development' === config.mode;
},
async setup (api) {
const hostname = server?.host ?? await findIp('v4');
let assetPrefix = options?.assetPrefix;
switch(typeof assetPrefix){
case 'string':
if (server?.port !== void 0) {
const hasPortPlaceholder = assetPrefix.includes('<port>');
if (!hasPortPlaceholder) {
const assetPrefixURL = new URL(assetPrefix);
if (assetPrefixURL.port !== String(server.port)) {
logger.warn(`Setting different port values in ${picocolors_default().cyan('server.port')} and ${picocolors_default().cyan('dev.assetPrefix')}. Using server.port(${picocolors_default().cyan(server.port)}) to make HMR work.`);
assetPrefixURL.port = String(server.port);
assetPrefix = assetPrefixURL.toString();
}
}
}
break;
case 'undefined':
case 'boolean':
if (options?.assetPrefix !== false) assetPrefix = `http://${hostname}:<port>/`;
break;
}
if (server?.base) {
if (assetPrefix.endsWith('/')) assetPrefix = assetPrefix.slice(0, -1);
assetPrefix = `${assetPrefix}${server.base}/`;
}
debug(`dev.assetPrefix is normalized to ${assetPrefix}`);
api.onBeforeStartDevServer(async ({ environments, server })=>{
if (environments['web']) {
const { createWebVirtualFilesMiddleware } = await import("@lynx-js/web-rsbuild-server-middleware");
server.middlewares.use(createWebVirtualFilesMiddleware('/__web_preview'));
}
});
api.modifyRsbuildConfig((config, { mergeRsbuildConfig })=>mergeRsbuildConfig(config, {
dev: {
assetPrefix,
client: {
host: hostname,
port: '<port>'
}
},
output: {
assetPrefix
}
}));
api.modifyRsbuildConfig((config, { mergeRsbuildConfig })=>{
const rspeedyAPIs = api.useExposed(Symbol.for('rspeedy.api'));
const defaultFilename = '[name].[platform].bundle';
const { filename } = rspeedyAPIs.config.output ?? {};
const bundle = 'object' == typeof filename ? filename.bundle ?? filename.template : filename;
const resolveName = (entry, platform)=>{
const resolved = 'function' == typeof bundle ? bundle({
lazyBundle: false,
entryName: entry,
platform
}) : bundle ?? defaultFilename;
return resolved.replaceAll('[name]', entry).replaceAll('[platform]', platform);
};
if (config.server?.printUrls === void 0 || config.server?.printUrls === true) {
const environmentNames = Object.keys(config.environments ?? {});
return mergeRsbuildConfig(config, {
server: {
printUrls: (param)=>{
const finalUrls = [];
const baseForUrls = ('string' == typeof assetPrefix ? assetPrefix : `http://${hostname}:<port>/`).replaceAll('<port>', String(param.port));
for (const entry of Object.keys(config.source?.entry ?? {}))for (const environmentName of environmentNames){
const pathname = resolveName(entry, environmentName);
finalUrls.push({
label: environmentName,
url: new URL(pathname, baseForUrls).toString()
});
if ('web' === environmentName) finalUrls.push({
label: "∟ Preview",
url: new URL(`/__web_preview?casename=${encodeURIComponent(pathname)}`, baseForUrls).toString()
});
}
return finalUrls.map((urlInfo)=>{
const label = urlInfo.label.charAt(0).toUpperCase() + urlInfo.label.slice(1);
urlInfo.label = label;
return urlInfo;
});
}
}
});
}
return config;
});
const require = createRequire(import.meta.url);
api.modifyBundlerChain((chain, { isDev, environment })=>{
const { action } = api.context;
if ('dev' !== action && !isDev) return;
const rsbuildPath = require.resolve('@rsbuild/core');
const rspeedyDir = node_path.dirname(require.resolve('@lynx-js/rspeedy/package.json'));
const searchParams = new URLSearchParams({
hostname,
port: api.context.devServer?.port?.toString() ?? '',
pathname: '/rsbuild-hmr',
hot: environment.config.dev?.hmr ?? true ? 'true' : 'false',
'live-reload': environment.config.dev?.liveReload ?? true ? 'true' : 'false',
protocol: 'ws'
});
if (environment.webSocketToken) searchParams.set('token', environment.webSocketToken);
chain.resolve.alias.set('webpack/hot/log.js', require.resolve('@rspack/core/hot/log', {
paths: [
rsbuildPath
]
})).set('@lynx-js/webpack-dev-transport/client', `${require.resolve('@lynx-js/webpack-dev-transport/client')}?${searchParams.toString()}`).set('@rspack/core/hot/emitter.js', require.resolve('@rspack/core/hot/emitter.js', {
paths: [
rsbuildPath
]
})).set('@rspack/core/hot/dev-server', require.resolve('@rspack/core/hot/dev-server', {
paths: [
rsbuildPath
]
})).end().end().plugin('lynx.hmr.provide.dev_server_client').use(ProvidePlugin_ProvidePlugin, [
{
__webpack_dev_server_client__: [
require.resolve('./client/hmr/WebSocketClient.js', {
paths: [
rspeedyDir
]
}),
'default'
]
}
]).end();
if (isLynx(environment)) chain.plugin('lynx.hmr.provide.websocket').use(ProvidePlugin_ProvidePlugin, [
{
WebSocket: [
options?.client?.websocketTransport ?? require.resolve('@lynx-js/websocket'),
'default'
]
}
]).end();
});
}
};
}
async function findIp(family, isInternal = false) {
const [{ default: ipaddr }, { default: os }] = await Promise.all([
import("./1~ipaddr.js").then(__webpack_require__.t.bind(__webpack_require__, "../../../node_modules/.pnpm/ipaddr.js@2.3.0/node_modules/ipaddr.js/lib/ipaddr.js", 23)),
import("node:os")
]);
let host;
const networks = Object.entries(os.networkInterfaces()).flatMap(([name, networks])=>(networks ?? []).map((network)=>({
name,
network
}))).filter(({ network })=>{
if (!network || !network.address) return false;
if (network.family !== `IP${family}`) return false;
if (network.internal !== isInternal) return false;
if ('v6' === family) {
const range = ipaddr.parse(network.address).range();
if ('ipv4Mapped' !== range && 'uniqueLocal' !== range) return false;
}
return true;
}).sort((left, right)=>getNetworkPriority(left.name, left.network.address) - getNetworkPriority(right.name, right.network.address));
if (networks.length > 0) {
host = networks[0].network.address;
if (host.includes(':')) host = `[${host}]`;
}
if (!host) throw new Error("No valid IP found");
return host;
}
function getNetworkPriority(name, address) {
const normalizedName = name.toLowerCase();
if (isPreferredInterface(normalizedName) && !isLinkLocalIpv4(address)) return 0;
if (!isVirtualInterface(normalizedName) && !isLinkLocalIpv4(address)) return 1;
if (!isVirtualInterface(normalizedName)) return 2;
return 3;
}
function isPreferredInterface(name) {
return /^(?:en\d+|eth\d+|eno\d+|enp\w+|wl\w+)$/.test(name);
}
function isVirtualInterface(name) {
return [
'utun',
'tun',
'tap',
'awdl',
'llw',
'lo'
].some((prefix)=>name.startsWith(prefix));
}
function isLinkLocalIpv4(address) {
return address.startsWith('169.254.');
}
export { pluginDev };