Skip to content

Commit

Permalink
feat: add EntryChunkPlugin to handle shebang and shims
Browse files Browse the repository at this point in the history
  • Loading branch information
fi3ework committed Nov 11, 2024
1 parent 95e1179 commit 3284409
Show file tree
Hide file tree
Showing 29 changed files with 551 additions and 58 deletions.
1 change: 1 addition & 0 deletions packages/core/rslib.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export default defineConfig({
entry: {
index: './src/index.ts',
libCssExtractLoader: './src/css/libCssExtractLoader.ts',
entryModuleLoader: './src/plugins/entryModuleLoader.ts',
},
define: {
RSLIB_VERSION: JSON.stringify(require('./package.json').version),
Expand Down
45 changes: 36 additions & 9 deletions packages/core/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
DEFAULT_CONFIG_NAME,
ENTRY_EXTENSIONS_PATTERN,
JS_EXTENSIONS_PATTERN,
RSLIB_ENTRY_QUERY,
SWC_HELPERS,
} from './constant';
import {
Expand All @@ -23,6 +24,7 @@ import {
cssExternalHandler,
isCssGlobalFile,
} from './css/cssConfig';
import { composePostEntryChunkConfig } from './plugins/PostEntryChunkPlugin';
import {
pluginCjsImportMetaUrlShim,
pluginEsmRequireShim,
Expand Down Expand Up @@ -596,7 +598,10 @@ const composeFormatConfig = ({
}
};

const composeShimsConfig = (format: Format, shims?: Shims): RsbuildConfig => {
const composeShimsConfig = (
format: Format,
shims?: Shims,
): { rsbuildConfig: RsbuildConfig; resolvedShims: Shims } => {
const resolvedShims = {
cjs: {
'import.meta.url': shims?.cjs?.['import.meta.url'] ?? true,
Expand All @@ -608,9 +613,10 @@ const composeShimsConfig = (format: Format, shims?: Shims): RsbuildConfig => {
},
};

let rsbuildConfig: RsbuildConfig = {};
switch (format) {
case 'esm':
return {
case 'esm': {
rsbuildConfig = {
tools: {
rspack: {
node: {
Expand All @@ -624,19 +630,23 @@ const composeShimsConfig = (format: Format, shims?: Shims): RsbuildConfig => {
Boolean,
),
};
break;
}
case 'cjs':
return {
rsbuildConfig = {
plugins: [
resolvedShims.cjs['import.meta.url'] && pluginCjsImportMetaUrlShim(),
].filter(Boolean),
};
break;
case 'umd':
return {};
case 'mf':
return {};
break;
default:
throw new Error(`Unsupported format: ${format}`);
}

return { rsbuildConfig, resolvedShims };
};

export const composeModuleImportWarn = (request: string): string => {
Expand Down Expand Up @@ -744,6 +754,16 @@ const composeSyntaxConfig = (
};
};

const appendEntryQuery = (
entry: NonNullable<RsbuildConfig['source']>['entry'],
): NonNullable<RsbuildConfig['source']>['entry'] => {
const newEntry: Record<string, string> = {};
for (const key in entry) {
newEntry[key] = `${entry[key]}?${RSLIB_ENTRY_QUERY}`;
}
return newEntry;
};

const composeEntryConfig = async (
entries: NonNullable<RsbuildConfig['source']>['entry'],
bundle: LibConfig['bundle'],
Expand All @@ -758,7 +778,7 @@ const composeEntryConfig = async (
return {
entryConfig: {
source: {
entry: entries,
entry: appendEntryQuery(entries),
},
},
lcp: null,
Expand Down Expand Up @@ -834,7 +854,7 @@ const composeEntryConfig = async (
const lcp = await calcLongestCommonPath(Object.values(resolvedEntries));
const entryConfig: RsbuildConfig = {
source: {
entry: resolvedEntries,
entry: appendEntryQuery(resolvedEntries),
},
};

Expand Down Expand Up @@ -1041,7 +1061,10 @@ async function composeLibRsbuildConfig(config: LibConfig, configPath: string) {
redirect = {},
umdName,
} = config;
const shimsConfig = composeShimsConfig(format!, shims);
const { rsbuildConfig: shimsConfig, resolvedShims } = composeShimsConfig(
format!,
shims,
);
const formatConfig = composeFormatConfig({
format: format!,
pkgJson: pkgJson!,
Expand Down Expand Up @@ -1084,6 +1107,9 @@ async function composeLibRsbuildConfig(config: LibConfig, configPath: string) {
cssModulesAuto,
);
const cssConfig = composeCssConfig(lcp, config.bundle);
const postEntryChunkConfig = composePostEntryChunkConfig({
importMetaUrlShim: !!resolvedShims?.cjs?.['import.meta.url'],
});
const dtsConfig = await composeDtsConfig(config, dtsExtension);
const externalsWarnConfig = composeExternalsWarnConfig(
format!,
Expand Down Expand Up @@ -1111,6 +1137,7 @@ async function composeLibRsbuildConfig(config: LibConfig, configPath: string) {
targetConfig,
entryConfig,
cssConfig,
postEntryChunkConfig,
minifyConfig,
dtsConfig,
bannerFooterConfig,
Expand Down
5 changes: 5 additions & 0 deletions packages/core/src/constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ export const DEFAULT_CONFIG_EXTENSIONS = [
] as const;

export const SWC_HELPERS = '@swc/helpers';
export const RSLIB_ENTRY_QUERY = '__rslib_entry__';
export const SHEBANG_PREFIX = '#!';
export const SHEBANG_REGEX: RegExp = /#!.*[\s\n\r]*/;
export const REACT_DIRECTIVE_REGEX: RegExp =
/^['"]use (client|server)['"](;?)$/;

export const JS_EXTENSIONS: string[] = [
'js',
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/css/cssConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,10 +110,10 @@ export function cssExternalHandler(
return false;
}

const pluginName = 'rsbuild:lib-css';
const PLUGIN_NAME = 'rsbuild:lib-css';

const pluginLibCss = (rootDir: string): RsbuildPlugin => ({
name: pluginName,
name: PLUGIN_NAME,
setup(api) {
api.modifyBundlerChain((config, { CHAIN_ID }) => {
let isUsingCssExtract = false;
Expand Down
218 changes: 218 additions & 0 deletions packages/core/src/plugins/PostEntryChunkPlugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
import { createRequire } from 'node:module';
import {
type RsbuildConfig,
type RsbuildPlugin,
type Rspack,
rspack,
} from '@rsbuild/core';
import {
JS_EXTENSIONS_PATTERN,
REACT_DIRECTIVE_REGEX,
SHEBANG_PREFIX,
SHEBANG_REGEX,
} from '../constant';
import { importMetaUrlShim } from './shims';
const require = createRequire(import.meta.url);

const PLUGIN_NAME = 'rsbuild:entry';

const matchFirstLine = (source: string, regex: RegExp) => {
const [firstLine] = source.split('\n');
if (!firstLine) {
return false;
}
const matched = regex.exec(firstLine);
if (!matched) {
return false;
}

return matched[0];
};

class PostEntryPlugin {
private enabledImportMetaUrlShim: boolean;
private shebangEntries: Record<string, string> = {};
private reactDirectives: Record<string, string> = {};
private importMetaUrlShims: Record<string, { startsWithUseStrict: boolean }> =
{};

constructor({
importMetaUrlShim = true,
}: {
importMetaUrlShim: boolean;
}) {
this.enabledImportMetaUrlShim = importMetaUrlShim;
}

apply(compiler: Rspack.Compiler) {
compiler.hooks.entryOption.tap(PLUGIN_NAME, (_context, entries) => {
for (const name in entries) {
const entry = (entries as Rspack.EntryStaticNormalized)[name];
if (!entry) continue;

let first: string | undefined;
if (Array.isArray(entry)) {
first = entry[0];
} else if (Array.isArray(entry.import)) {
first = entry.import[0];
} else if (typeof entry === 'string') {
first = entry;
}

if (typeof first !== 'string') continue;

const filename = first.split('?')[0]!;
const isJs = JS_EXTENSIONS_PATTERN.test(filename);
if (!isJs) continue;

const content = compiler.inputFileSystem!.readFileSync!(filename, {
encoding: 'utf-8',
});

// Shebang
if (content.startsWith(SHEBANG_PREFIX)) {
const shebangMatch = matchFirstLine(content, SHEBANG_REGEX);
if (shebangMatch) {
this.shebangEntries[name] = shebangMatch;
}
}

// React directive
const reactDirective = matchFirstLine(content, REACT_DIRECTIVE_REGEX);
if (reactDirective) {
this.reactDirectives[name] = reactDirective;
}

// import.meta.url shim
if (this.enabledImportMetaUrlShim) {
this.importMetaUrlShims[name] = {
startsWithUseStrict:
// This is a hypothesis that no comments will occur before "use strict;".
// But it should cover most cases.
content.startsWith('use strict;') ||
content.startsWith('"use strict";'),
};
}
}
});

compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => {
compilation.hooks.chunkAsset.tap(PLUGIN_NAME, (chunk, filename) => {
const isJs = JS_EXTENSIONS_PATTERN.test(filename);
if (!isJs) return;

const name = chunk.name;
if (!name) return;

const shebangEntry = this.shebangEntries[name];
if (shebangEntry) {
this.shebangEntries[filename] = shebangEntry;
}

const reactDirective = this.reactDirectives[name];
if (reactDirective) {
this.reactDirectives[filename] = reactDirective;
}

const importMetaUrlShimInfo = this.importMetaUrlShims[name];
if (importMetaUrlShimInfo) {
this.importMetaUrlShims[filename] = importMetaUrlShimInfo;
}
});
});

compiler.hooks.make.tap(PLUGIN_NAME, (compilation) => {
compilation.hooks.processAssets.tap(PLUGIN_NAME, (assets) => {
const chunkAsset = Object.keys(assets);
for (const name of chunkAsset) {
if (this.enabledImportMetaUrlShim) {
compilation.updateAsset(name, (old) => {
const importMetaUrlShimInfo = this.importMetaUrlShims[name];
if (importMetaUrlShimInfo) {
const replaceSource = new rspack.sources.ReplaceSource(old);

if (importMetaUrlShimInfo.startsWithUseStrict) {
replaceSource.replace(
0,
11, // 'use strict;'.length,
`"use strict";\n${importMetaUrlShim}`,
);
} else {
replaceSource.insert(0, importMetaUrlShim);
}

return replaceSource;
}

return old;
});
}
}
});

compilation.hooks.processAssets.tap(
{
name: PLUGIN_NAME,
// Just after minify stage, to avoid from being minified.
stage: rspack.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE + 1,
},
(assets) => {
const chunkAsset = Object.keys(assets);
for (const name of chunkAsset) {
const shebangValue = this.shebangEntries[name];
const reactDirectiveValue = this.reactDirectives[name];

if (shebangValue || reactDirectiveValue) {
compilation.updateAsset(name, (old) => {
const replaceSource = new rspack.sources.ReplaceSource(old);
// Shebang
if (shebangValue) {
replaceSource.insert(0, `${shebangValue}\n`);
}

// React directives
if (reactDirectiveValue) {
replaceSource.insert(0, `${reactDirectiveValue}\n`);
}

return replaceSource;
});
}
}
},
);
});
}
}

const entryModuleLoaderPlugin = (): RsbuildPlugin => ({
name: PLUGIN_NAME,
setup(api) {
api.modifyBundlerChain((config, { CHAIN_ID }) => {
const rule = config.module.rule(CHAIN_ID.RULE.JS);
rule
.use('shebang')
.loader(require.resolve('./entryModuleLoader.js'))
.options({});
});
},
});

export const composePostEntryChunkConfig = ({
importMetaUrlShim,
}: {
importMetaUrlShim: boolean;
}): RsbuildConfig => {
return {
plugins: [entryModuleLoaderPlugin()],
tools: {
rspack: {
plugins: [
new PostEntryPlugin({
importMetaUrlShim: importMetaUrlShim,
}),
],
},
},
};
};
Loading

0 comments on commit 3284409

Please sign in to comment.