Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support add plugins for specified environment #2986

Merged
merged 10 commits into from
Jul 23, 2024
Merged
60 changes: 60 additions & 0 deletions e2e/cases/environments/plugins/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { build, gotoPage } from '@e2e/helper';
import { expect, test } from '@playwright/test';

import { pluginReact } from '@rsbuild/plugin-react';

test('should add single environment plugin correctly', async ({ page }) => {
const rsbuild = await build({
cwd: __dirname,
rsbuildConfig: {
environments: {
web: {
output: {
filenameHash: false,
},
plugins: [pluginReact()],
},
web1: {
source: {
entry: {
main: './src/index1.ts',
},
},
output: {
assetPrefix: 'auto',
filenameHash: false,
distPath: {
root: 'dist/web1',
},
},
},
},
},
runServer: true,
});

await gotoPage(page, rsbuild);

const button = page.locator('#test');
await expect(button).toHaveText('Hello Rsbuild!');

const web1Url = new URL(`http://localhost:${rsbuild.port}/web1/main`);

await page.goto(web1Url.href);

await expect(page.locator('#test1')).toHaveText('Hello Rsbuild!');

const files = await rsbuild.unwrapOutputJSON();
const filenames = Object.keys(files);

expect(
filenames.some((filename) =>
filename.includes('dist/static/js/lib-react.js'),
),
).toBeTruthy();
expect(
filenames.some((filename) =>
filename.includes('dist/web1/static/js/lib-react.js'),
),
).toBeFalsy();
});
3 changes: 3 additions & 0 deletions e2e/cases/environments/plugins/src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const App = () => <div id="test">Hello Rsbuild!</div>;

export default App;
9 changes: 9 additions & 0 deletions e2e/cases/environments/plugins/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import React from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';

const container = document.getElementById('root');
if (container) {
const root = createRoot(container);
root.render(React.createElement(App));
}
5 changes: 5 additions & 0 deletions e2e/cases/environments/plugins/src/index1.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
document.getElementById('root').innerHTML = `
<div>
<div id="test1">Hello Rsbuild!</div>
</div>
`;
18 changes: 10 additions & 8 deletions packages/compat/webpack/src/webpackConfig.ts
Original file line number Diff line number Diff line change
@@ -26,10 +26,11 @@ async function modifyWebpackChain(
): Promise<RspackChain> {
logger.debug('modify webpack chain');

const [modifiedChain] = await context.hooks.modifyWebpackChain.call(
chain,
utils,
);
const [modifiedChain] =
await context.hooks.modifyWebpackChain.callInEnvironment({
environment: utils.environment.name,
args: [chain, utils],
});

if (utils.environment.config.tools?.webpackChain) {
for (const item of castArray(utils.environment.config.tools.webpackChain)) {
@@ -48,10 +49,11 @@ async function modifyWebpackConfig(
utils: ModifyWebpackConfigUtils,
): Promise<WebpackConfig> {
logger.debug('modify webpack config');
let [modifiedConfig] = await context.hooks.modifyWebpackConfig.call(
webpackConfig,
utils,
);
let [modifiedConfig] =
await context.hooks.modifyWebpackConfig.callInEnvironment({
environment: utils.environment.name,
args: [webpackConfig, utils],
});

if (utils.environment.config.tools?.webpack) {
modifiedConfig = reduceConfigsWithContext({
4 changes: 3 additions & 1 deletion packages/core/src/config.ts
Original file line number Diff line number Diff line change
@@ -458,7 +458,9 @@ export const getRsbuildInspectConfig = ({
for (const [name, config] of Object.entries(environments)) {
const debugConfig = {
...config,
pluginNames,
pluginNames: pluginManager
.getPlugins({ environment: name })
.map((p) => p.name),
};
rawEnvironmentConfigs.push({
name,
21 changes: 7 additions & 14 deletions packages/core/src/configChain.ts
Original file line number Diff line number Diff line change
@@ -2,11 +2,8 @@ import RspackChain from 'rspack-chain';
import { castArray, isPlainObject } from './helpers';
import { logger } from './logger';
import type {
CreateAsyncHook,
ModifyBundlerChainFn,
InternalContext,
ModifyBundlerChainUtils,
RsbuildConfig,
RsbuildContext,
RsbuildEntry,
Rspack,
RspackConfig,
@@ -19,22 +16,18 @@ export function getBundlerChain(): RspackChain {
}

export async function modifyBundlerChain(
context: RsbuildContext & {
hooks: {
modifyBundlerChain: CreateAsyncHook<ModifyBundlerChainFn>;
};
config: Readonly<RsbuildConfig>;
},
context: InternalContext,
utils: ModifyBundlerChainUtils,
): Promise<RspackChain> {
logger.debug('modify bundler chain');

const bundlerChain = getBundlerChain();

const [modifiedBundlerChain] = await context.hooks.modifyBundlerChain.call(
bundlerChain,
utils,
);
const [modifiedBundlerChain] =
await context.hooks.modifyBundlerChain.callInEnvironment({
environment: utils.environment.name,
args: [bundlerChain, utils],
});

if (utils.environment.config.tools?.bundlerChain) {
for (const item of castArray(utils.environment.config.tools.bundlerChain)) {
32 changes: 25 additions & 7 deletions packages/core/src/createRsbuild.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { createContext } from './createContext';
import { pick } from './helpers';
import { getPluginAPI } from './initPlugins';
import { initPluginAPI } from './initPlugins';
import { initRsbuildConfig } from './internal';
import { logger } from './logger';
import { setCssExtractPlugin } from './pluginHelper';
@@ -37,8 +37,11 @@ async function applyDefaultPlugins(
),
import('./plugins/asset').then(({ pluginAsset }) => pluginAsset()),
import('./plugins/html').then(({ pluginHtml }) =>
pluginHtml(async (...args) => {
const result = await context.hooks.modifyHTMLTags.call(...args);
pluginHtml((environment: string) => async (...args) => {
const result = await context.hooks.modifyHTMLTags.callInEnvironment({
environment,
args,
});
return result[0];
}),
),
@@ -110,8 +113,9 @@ export async function createRsbuild(
rsbuildConfig.provider ? 'webpack' : 'rspack',
);

const pluginAPI = getPluginAPI({ context, pluginManager });
context.pluginAPI = pluginAPI;
const getPluginAPI = initPluginAPI({ context, pluginManager });
context.getPluginAPI = getPluginAPI;
const globalPluginAPI = getPluginAPI();

logger.debug('add default plugins');
await applyDefaultPlugins(pluginManager, context);
@@ -140,7 +144,7 @@ export async function createRsbuild(
'removePlugins',
'isPluginExists',
]),
...pick(pluginAPI, [
...pick(globalPluginAPI, [
'onBeforeBuild',
'onBeforeCreateCompiler',
'onBeforeStartDevServer',
@@ -164,13 +168,27 @@ export async function createRsbuild(
'startDevServer',
]),
preview,
context: pluginAPI.context,
context: globalPluginAPI.context,
};

if (rsbuildConfig.plugins) {
const plugins = await Promise.all(rsbuildConfig.plugins);
rsbuild.addPlugins(plugins);
}

// Register environment plugin
if (rsbuildConfig.environments) {
await Promise.all(
Object.entries(rsbuildConfig.environments).map(async ([name, config]) => {
if (config.plugins) {
const plugins = await Promise.all(config.plugins);
rsbuild.addPlugins(plugins, {
environment: name,
});
}
}),
);
}

return rsbuild;
}
110 changes: 96 additions & 14 deletions packages/core/src/initHooks.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { isFunction } from './helpers';
import { isPluginMatchEnvironment } from './pluginManager';
import type {
AsyncHook,
EnvironmentAsyncHook,
HookDescriptor,
ModifyBundlerChainFn,
ModifyEnvironmentConfigFn,
@@ -22,6 +24,84 @@ import type {
OnExitFn,
} from './types';

export function createEnvironmentAsyncHook<
Callback extends (...args: any[]) => any,
>(): EnvironmentAsyncHook<Callback> {
type Hook = {
environment?: string;
handler: Callback;
};
const preGroup: Hook[] = [];
const postGroup: Hook[] = [];
const defaultGroup: Hook[] = [];

const tapEnvironment = ({
environment,
handler: cb,
}: {
environment?: string;
handler: Callback | HookDescriptor<Callback>;
}) => {
if (isFunction(cb)) {
defaultGroup.push({
environment,
handler: cb,
});
} else if (cb.order === 'pre') {
preGroup.push({
environment,
handler: cb.handler,
});
} else if (cb.order === 'post') {
postGroup.push({
environment,
handler: cb.handler,
});
} else {
defaultGroup.push({
environment,
handler: cb.handler,
});
}
};

const callInEnvironment = async ({
environment,
args: params,
}: {
environment?: string;
args: Parameters<Callback>;
}) => {
const callbacks = [...preGroup, ...defaultGroup, ...postGroup];

for (const callback of callbacks) {
// If this callback is not a global callback, the environment info should match
if (
callback.environment &&
environment &&
!isPluginMatchEnvironment(callback.environment, environment)
) {
continue;
}

const result = await callback.handler(...params);

if (result !== undefined) {
params[0] = result;
}
}

return params;
};

return {
tapEnvironment,
tap: (handler: Callback | HookDescriptor<Callback>) =>
tapEnvironment({ handler }),
callInEnvironment,
};
}

export function createAsyncHook<
Callback extends (...args: any[]) => any,
>(): AsyncHook<Callback> {
@@ -41,8 +121,7 @@ export function createAsyncHook<
}
};

const call = async (...args: Parameters<Callback>) => {
const params = args.slice(0) as Parameters<Callback>;
const call = async (...params: Parameters<Callback>) => {
const callbacks = [...preGroup, ...defaultGroup, ...postGroup];

for (const callback of callbacks) {
@@ -63,6 +142,7 @@ export function createAsyncHook<
}

export function initHooks(): {
/** The following hooks are global hooks */
onExit: AsyncHook<OnExitFn>;
onAfterBuild: AsyncHook<OnAfterBuildFn>;
onBeforeBuild: AsyncHook<OnBeforeBuildFn>;
@@ -74,13 +154,14 @@ export function initHooks(): {
onBeforeStartProdServer: AsyncHook<OnBeforeStartProdServerFn>;
onAfterCreateCompiler: AsyncHook<OnAfterCreateCompilerFn>;
onBeforeCreateCompiler: AsyncHook<OnBeforeCreateCompilerFn>;
modifyHTMLTags: AsyncHook<ModifyHTMLTagsFn>;
modifyRspackConfig: AsyncHook<ModifyRspackConfigFn>;
modifyBundlerChain: AsyncHook<ModifyBundlerChainFn>;
modifyWebpackChain: AsyncHook<ModifyWebpackChainFn>;
modifyWebpackConfig: AsyncHook<ModifyWebpackConfigFn>;
/** The following hooks are related to the environment */
modifyHTMLTags: EnvironmentAsyncHook<ModifyHTMLTagsFn>;
modifyRspackConfig: EnvironmentAsyncHook<ModifyRspackConfigFn>;
modifyBundlerChain: EnvironmentAsyncHook<ModifyBundlerChainFn>;
modifyWebpackChain: EnvironmentAsyncHook<ModifyWebpackChainFn>;
modifyWebpackConfig: EnvironmentAsyncHook<ModifyWebpackConfigFn>;
modifyRsbuildConfig: AsyncHook<ModifyRsbuildConfigFn>;
modifyEnvironmentConfig: AsyncHook<ModifyEnvironmentConfigFn>;
modifyEnvironmentConfig: EnvironmentAsyncHook<ModifyEnvironmentConfigFn>;
} {
return {
onExit: createAsyncHook<OnExitFn>(),
@@ -94,13 +175,14 @@ export function initHooks(): {
onBeforeStartProdServer: createAsyncHook<OnBeforeStartProdServerFn>(),
onAfterCreateCompiler: createAsyncHook<OnAfterCreateCompilerFn>(),
onBeforeCreateCompiler: createAsyncHook<OnBeforeCreateCompilerFn>(),
modifyHTMLTags: createAsyncHook<ModifyHTMLTagsFn>(),
modifyRspackConfig: createAsyncHook<ModifyRspackConfigFn>(),
modifyBundlerChain: createAsyncHook<ModifyBundlerChainFn>(),
modifyWebpackChain: createAsyncHook<ModifyWebpackChainFn>(),
modifyWebpackConfig: createAsyncHook<ModifyWebpackConfigFn>(),
modifyHTMLTags: createEnvironmentAsyncHook<ModifyHTMLTagsFn>(),
modifyRspackConfig: createEnvironmentAsyncHook<ModifyRspackConfigFn>(),
modifyBundlerChain: createEnvironmentAsyncHook<ModifyBundlerChainFn>(),
modifyWebpackChain: createEnvironmentAsyncHook<ModifyWebpackChainFn>(),
modifyWebpackConfig: createEnvironmentAsyncHook<ModifyWebpackConfigFn>(),
modifyRsbuildConfig: createAsyncHook<ModifyRsbuildConfigFn>(),
modifyEnvironmentConfig: createAsyncHook<ModifyEnvironmentConfigFn>(),
modifyEnvironmentConfig:
createEnvironmentAsyncHook<ModifyEnvironmentConfigFn>(),
};
}

Loading