Skip to content

Commit 3c6d306

Browse files
committed
fix: handle add and unlink file in bundleless mode
1 parent 3564bcb commit 3c6d306

File tree

9 files changed

+230
-94
lines changed

9 files changed

+230
-94
lines changed

packages/core/src/config.ts

Lines changed: 72 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -918,76 +918,90 @@ const composeEntryConfig = async (
918918
};
919919
}
920920

921-
// In bundleless mode, resolve glob patterns and convert them to entry object.
922-
const resolvedEntries: Record<string, string> = {};
923-
for (const key of Object.keys(entries)) {
924-
const entry = entries[key];
925-
926-
// Entries in bundleless mode could be:
927-
// 1. A string of glob pattern: { entry: { index: 'src/*.ts' } }
928-
// 2. An array of glob patterns: { entry: { index: ['src/*.ts', 'src/*.tsx'] } }
929-
// Not supported for now: entry description object
930-
const entryFiles = Array.isArray(entry)
931-
? entry
932-
: typeof entry === 'string'
933-
? [entry]
934-
: null;
935-
936-
if (!entryFiles) {
937-
throw new Error(
938-
'Entry can only be a string or an array of strings for now',
939-
);
940-
}
921+
const globScanEntries = async (calcLcp: boolean) => {
922+
// In bundleless mode, resolve glob patterns and convert them to entry object.
923+
const resolvedEntries: Record<string, string> = {};
924+
for (const key of Object.keys(entries)) {
925+
const entry = entries[key];
926+
927+
// Entries in bundleless mode could be:
928+
// 1. A string of glob pattern: { entry: { index: 'src/*.ts' } }
929+
// 2. An array of glob patterns: { entry: { index: ['src/*.ts', 'src/*.tsx'] } }
930+
// Not supported for now: entry description object
931+
const entryFiles = Array.isArray(entry)
932+
? entry
933+
: typeof entry === 'string'
934+
? [entry]
935+
: null;
941936

942-
// Turn entries in array into each separate entry.
943-
const globEntryFiles = await glob(entryFiles, {
944-
cwd: root,
945-
absolute: true,
946-
});
937+
if (!entryFiles) {
938+
throw new Error(
939+
'Entry can only be a string or an array of strings for now',
940+
);
941+
}
947942

948-
// Filter the glob resolved entry files based on the allowed extensions
949-
const resolvedEntryFiles = globEntryFiles.filter((file) =>
950-
ENTRY_EXTENSIONS_PATTERN.test(file),
951-
);
943+
// Turn entries in array into each separate entry.
944+
const globEntryFiles = await glob(entryFiles, {
945+
cwd: root,
946+
absolute: true,
947+
});
952948

953-
if (resolvedEntryFiles.length === 0) {
954-
throw new Error(`Cannot find ${resolvedEntryFiles}`);
955-
}
949+
// Filter the glob resolved entry files based on the allowed extensions
950+
const resolvedEntryFiles = globEntryFiles.filter((file) =>
951+
ENTRY_EXTENSIONS_PATTERN.test(file),
952+
);
953+
954+
if (resolvedEntryFiles.length === 0) {
955+
throw new Error(`Cannot find ${resolvedEntryFiles}`);
956+
}
957+
958+
// Similar to `rootDir` in tsconfig and `outbase` in esbuild.
959+
const lcp = await calcLongestCommonPath(resolvedEntryFiles);
960+
// Using the longest common path of all non-declaration input files by default.
961+
const outBase = lcp === null ? root : lcp;
956962

957-
// Similar to `rootDir` in tsconfig and `outbase` in esbuild.
958-
const lcp = await calcLongestCommonPath(resolvedEntryFiles);
959-
// Using the longest common path of all non-declaration input files by default.
960-
const outBase = lcp === null ? root : lcp;
963+
function getEntryName(file: string) {
964+
const { dir, name } = path.parse(path.relative(outBase, file));
965+
// Entry filename contains nested path to preserve source directory structure.
966+
const entryFileName = path.join(dir, name);
961967

962-
function getEntryName(file: string) {
963-
const { dir, name } = path.parse(path.relative(outBase, file));
964-
// Entry filename contains nested path to preserve source directory structure.
965-
const entryFileName = path.join(dir, name);
968+
// 1. we mark the global css files (which will generate empty js chunk in cssExtract), and deleteAsset in RemoveCssExtractAssetPlugin
969+
// 2. avoid the same name e.g: `index.ts` and `index.css`
970+
if (isCssGlobalFile(file, cssModulesAuto)) {
971+
return `${RSLIB_CSS_ENTRY_FLAG}/${entryFileName}`;
972+
}
966973

967-
// 1. we mark the global css files (which will generate empty js chunk in cssExtract), and deleteAsset in RemoveCssExtractAssetPlugin
968-
// 2. avoid the same name e.g: `index.ts` and `index.css`
969-
if (isCssGlobalFile(file, cssModulesAuto)) {
970-
return `${RSLIB_CSS_ENTRY_FLAG}/${entryFileName}`;
974+
return entryFileName;
971975
}
972976

973-
return entryFileName;
977+
for (const file of resolvedEntryFiles) {
978+
const entryName = getEntryName(file);
979+
if (resolvedEntries[entryName]) {
980+
logger.warn(
981+
`duplicate entry: ${entryName}, this may lead to the incorrect output, please rename the file`,
982+
);
983+
}
984+
resolvedEntries[entryName] = file;
985+
}
974986
}
975987

976-
for (const file of resolvedEntryFiles) {
977-
const entryName = getEntryName(file);
978-
if (resolvedEntries[entryName]) {
979-
logger.warn(
980-
`duplicate entry: ${entryName}, this may lead to the incorrect output, please rename the file`,
981-
);
982-
}
983-
resolvedEntries[entryName] = file;
988+
if (calcLcp) {
989+
const lcp = await calcLongestCommonPath(Object.values(resolvedEntries));
990+
return { resolvedEntries, lcp };
984991
}
985-
}
992+
return { resolvedEntries, lcp: null };
993+
};
986994

987-
const lcp = await calcLongestCommonPath(Object.values(resolvedEntries));
995+
// LCP could only be determined at the first time of glob scan.
996+
const { lcp } = await globScanEntries(true);
988997
const entryConfig: EnvironmentConfig = {
989-
source: {
990-
entry: appendEntryQuery(resolvedEntries),
998+
tools: {
999+
rspack: {
1000+
entry: async () => {
1001+
const { resolvedEntries } = await globScanEntries(false);
1002+
return appendEntryQuery(resolvedEntries);
1003+
},
1004+
},
9911005
},
9921006
};
9931007

@@ -1342,6 +1356,7 @@ async function composeLibRsbuildConfig(
13421356

13431357
const entryChunkConfig = composeEntryChunkConfig({
13441358
enabledImportMetaUrlShim: enabledShims.cjs['import.meta.url'],
1359+
contextToWatch: lcp,
13451360
});
13461361
const dtsConfig = await composeDtsConfig(config, dtsExtension);
13471362
const externalsWarnConfig = composeExternalsWarnConfig(

packages/core/src/plugins/EntryChunkPlugin.ts

Lines changed: 45 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -37,48 +37,54 @@ class EntryChunkPlugin {
3737
private shebangInjectedAssets: Set<string> = new Set();
3838

3939
private enabledImportMetaUrlShim: boolean;
40+
private contextToWatch: string | null = null;
41+
private contextWatched = false;
4042

4143
constructor({
4244
enabledImportMetaUrlShim = true,
45+
contextToWatch,
4346
}: {
4447
enabledImportMetaUrlShim: boolean;
48+
contextToWatch: string | null;
4549
}) {
4650
this.enabledImportMetaUrlShim = enabledImportMetaUrlShim;
51+
this.contextToWatch = contextToWatch;
4752
}
4853

4954
apply(compiler: Rspack.Compiler) {
50-
compiler.hooks.entryOption.tap(PLUGIN_NAME, (_context, entries) => {
51-
for (const name in entries) {
52-
const entry = (entries as Rspack.EntryStaticNormalized)[name];
53-
if (!entry) continue;
54-
55-
let first: string | undefined;
56-
if (Array.isArray(entry)) {
57-
first = entry[0];
58-
} else if (Array.isArray(entry.import)) {
59-
first = entry.import[0];
60-
} else if (typeof entry === 'string') {
61-
first = entry;
62-
}
55+
compiler.hooks.afterCompile.tap(PLUGIN_NAME, (compilation) => {
56+
if (this.contextWatched || this.contextToWatch === null) return;
57+
58+
const contextDep = compilation.contextDependencies;
59+
contextDep.add(this.contextToWatch);
60+
this.contextWatched = true;
61+
});
6362

64-
if (typeof first !== 'string') continue;
63+
compiler.hooks.make.tap(PLUGIN_NAME, (compilation) => {
64+
const entries: Record<string, string> = {};
65+
for (const [key, value] of compilation.entries) {
66+
const firstDep = value.dependencies[0];
67+
if (firstDep?.request) {
68+
entries[key] = firstDep.request;
69+
}
70+
}
6571

72+
for (const name in entries) {
73+
const first = entries[name];
74+
if (!first) continue;
6675
const filename = first.split('?')[0]!;
6776
const isJs = JS_EXTENSIONS_PATTERN.test(filename);
6877
if (!isJs) continue;
69-
7078
const content = compiler.inputFileSystem!.readFileSync!(filename, {
7179
encoding: 'utf-8',
7280
});
73-
7481
// Shebang
7582
if (content.startsWith(SHEBANG_PREFIX)) {
7683
const shebangMatch = matchFirstLine(content, SHEBANG_REGEX);
7784
if (shebangMatch) {
7885
this.shebangEntries[name] = shebangMatch;
7986
}
8087
}
81-
8288
// React directive
8389
const reactDirective = matchFirstLine(content, REACT_DIRECTIVE_REGEX);
8490
if (reactDirective) {
@@ -87,7 +93,25 @@ class EntryChunkPlugin {
8793
}
8894
});
8995

90-
compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => {
96+
compiler.hooks.make.tap(PLUGIN_NAME, (compilation) => {
97+
compilation.hooks.chunkAsset.tap(PLUGIN_NAME, (chunk, filename) => {
98+
const isJs = JS_EXTENSIONS_PATTERN.test(filename);
99+
if (!isJs) return;
100+
101+
const name = chunk.name;
102+
if (!name) return;
103+
104+
const shebangEntry = this.shebangEntries[name];
105+
if (shebangEntry) {
106+
this.shebangEntries[filename] = shebangEntry;
107+
}
108+
109+
const reactDirective = this.reactDirectives[name];
110+
if (reactDirective) {
111+
this.reactDirectives[filename] = reactDirective;
112+
}
113+
});
114+
91115
compilation.hooks.chunkAsset.tap(PLUGIN_NAME, (chunk, filename) => {
92116
const isJs = JS_EXTENSIONS_PATTERN.test(filename);
93117
if (!isJs) return;
@@ -192,8 +216,10 @@ const entryModuleLoaderRsbuildPlugin = (): RsbuildPlugin => ({
192216

193217
export const composeEntryChunkConfig = ({
194218
enabledImportMetaUrlShim,
219+
contextToWatch = null,
195220
}: {
196221
enabledImportMetaUrlShim: boolean;
222+
contextToWatch: string | null;
197223
}): EnvironmentConfig => {
198224
return {
199225
plugins: [entryModuleLoaderRsbuildPlugin()],
@@ -202,6 +228,7 @@ export const composeEntryChunkConfig = ({
202228
plugins: [
203229
new EntryChunkPlugin({
204230
enabledImportMetaUrlShim,
231+
contextToWatch,
205232
}),
206233
],
207234
},

packages/core/tests/__snapshots__/config.test.ts.snap

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,8 @@ exports[`Should compose create Rsbuild config correctly > Merge Rsbuild config i
216216
{
217217
"plugins": [
218218
EntryChunkPlugin {
219+
"contextToWatch": null,
220+
"contextWatched": false,
219221
"enabledImportMetaUrlShim": false,
220222
"reactDirectives": {},
221223
"shebangChmod": 493,
@@ -449,6 +451,8 @@ exports[`Should compose create Rsbuild config correctly > Merge Rsbuild config i
449451
{
450452
"plugins": [
451453
EntryChunkPlugin {
454+
"contextToWatch": null,
455+
"contextWatched": false,
452456
"enabledImportMetaUrlShim": true,
453457
"reactDirectives": {},
454458
"shebangChmod": 493,
@@ -668,6 +672,8 @@ exports[`Should compose create Rsbuild config correctly > Merge Rsbuild config i
668672
{
669673
"plugins": [
670674
EntryChunkPlugin {
675+
"contextToWatch": null,
676+
"contextWatched": false,
671677
"enabledImportMetaUrlShim": false,
672678
"reactDirectives": {},
673679
"shebangChmod": 493,
@@ -822,6 +828,8 @@ exports[`Should compose create Rsbuild config correctly > Merge Rsbuild config i
822828
{
823829
"plugins": [
824830
EntryChunkPlugin {
831+
"contextToWatch": null,
832+
"contextWatched": false,
825833
"enabledImportMetaUrlShim": false,
826834
"reactDirectives": {},
827835
"shebangChmod": 493,

0 commit comments

Comments
 (0)