diff --git a/packages/core/src/config.ts b/packages/core/src/config.ts index a8f4d7f45..6c2bad4dd 100644 --- a/packages/core/src/config.ts +++ b/packages/core/src/config.ts @@ -414,6 +414,7 @@ export function composeMinifyConfig(config: LibConfig): EnvironmentConfig { // MF assets are loaded over the network, which means they will not be compressed by the project. Therefore, minifying them is necessary. minify: format === 'mf', compress: { + directives: false, defaults: false, unused: true, dead_code: true, @@ -1831,6 +1832,7 @@ async function composeLibRsbuildConfig( const assetConfig = composeAssetConfig(bundle, format); const entryChunkConfig = composeEntryChunkConfig({ + useLoader: advancedEsm !== true, enabledImportMetaUrlShim: enabledShims.cjs['import.meta.url'], contextToWatch: outBase, }); diff --git a/packages/core/src/constant.ts b/packages/core/src/constant.ts index 2528c4ff0..3e4bae461 100644 --- a/packages/core/src/constant.ts +++ b/packages/core/src/constant.ts @@ -10,10 +10,6 @@ export const DEFAULT_CONFIG_EXTENSIONS = [ ] as const; export const SWC_HELPERS = '@swc/helpers'; -export const SHEBANG_PREFIX = '#!'; -export const SHEBANG_REGEX: RegExp = /#!.*[\s\n\r]*$/; -export const REACT_DIRECTIVE_REGEX: RegExp = - /^['"]use (client|server)['"](;?)[\s\n\r]*$/; const DTS_EXTENSIONS: string[] = ['d.ts', 'd.mts', 'd.cts']; diff --git a/packages/core/src/plugins/EntryChunkPlugin.ts b/packages/core/src/plugins/EntryChunkPlugin.ts index 8e9ed2185..6889b38bf 100644 --- a/packages/core/src/plugins/EntryChunkPlugin.ts +++ b/packages/core/src/plugins/EntryChunkPlugin.ts @@ -1,4 +1,3 @@ -import { chmodSync } from 'node:fs'; import { createRequire } from 'node:module'; import { type EnvironmentConfig, @@ -6,12 +5,7 @@ import { type Rspack, rspack, } from '@rsbuild/core'; -import { - JS_EXTENSIONS_PATTERN, - REACT_DIRECTIVE_REGEX, - SHEBANG_PREFIX, - SHEBANG_REGEX, -} from '../constant'; +import { JS_EXTENSIONS_PATTERN } from '../constant'; const require = createRequire(import.meta.url); @@ -25,26 +19,9 @@ const IMPORT_META_URL_SHIM = `const __rslib_import_meta_url__ = /*#__PURE__*/ (f })(); `; -const matchFirstLine = (source: string, regex: RegExp): string | false => { - const lineBreakPos = source.match(/(\r\n|\n)/); - const firstLineContent = source.slice(0, lineBreakPos?.index); - const matched = regex.exec(firstLineContent); - if (!matched) { - return false; - } - - return matched[0]; -}; - class EntryChunkPlugin { - private reactDirectives: Record = {}; - private shimsInjectedAssets: Set = new Set(); - private shebangChmod = 0o755; - private shebangEntries: Record = {}; - private shebangInjectedAssets: Set = new Set(); - private enabledImportMetaUrlShim: boolean; private contextToWatch: string | null = null; @@ -72,57 +49,11 @@ class EntryChunkPlugin { }); compiler.hooks.make.tap(PLUGIN_NAME, (compilation) => { - const entries: Record = {}; - for (const [key, value] of compilation.entries) { - const firstDep = value.dependencies[0]; - if (firstDep?.request) { - entries[key] = firstDep.request; - } - } - - for (const name in entries) { - const first = entries[name]; - if (!first) 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; - } - } - }); - - compiler.hooks.make.tap(PLUGIN_NAME, (compilation) => { - compilation.hooks.chunkAsset.tap(PLUGIN_NAME, (chunk, filename) => { + compilation.hooks.chunkAsset.tap(PLUGIN_NAME, (_chunk, filename) => { const isJs = JS_EXTENSIONS_PATTERN.test(filename); if (!isJs) return; this.shimsInjectedAssets.add(filename); - - 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; - } }); }); @@ -158,45 +89,6 @@ class EntryChunkPlugin { }); } }); - - compilation.hooks.processAssets.tap( - { - name: PLUGIN_NAME, - // Just after minify stage, to avoid from being minified. - stage: rspack.Compilation.PROCESS_ASSETS_STAGE_DEV_TOOLING - 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`); - this.shebangInjectedAssets.add(name); - } - - // React directives - if (reactDirectiveValue) { - replaceSource.insert(0, `${reactDirectiveValue}\n`); - } - - return replaceSource; - }); - } - } - }, - ); - }); - - compiler.hooks.assetEmitted.tap(PLUGIN_NAME, (file, { targetPath }) => { - if (this.shebangInjectedAssets.has(file)) { - chmodSync(targetPath, this.shebangChmod); - } }); } } @@ -217,13 +109,15 @@ const entryModuleLoaderRsbuildPlugin = (): RsbuildPlugin => ({ export const composeEntryChunkConfig = ({ enabledImportMetaUrlShim, + useLoader, contextToWatch = null, }: { + useLoader: boolean; enabledImportMetaUrlShim: boolean; contextToWatch: string | null; }): EnvironmentConfig => { return { - plugins: [entryModuleLoaderRsbuildPlugin()], + plugins: useLoader ? [entryModuleLoaderRsbuildPlugin()] : [], tools: { rspack: { plugins: [ diff --git a/packages/core/src/plugins/entryModuleLoader.ts b/packages/core/src/plugins/entryModuleLoader.ts index bd62642a9..600f93e2a 100644 --- a/packages/core/src/plugins/entryModuleLoader.ts +++ b/packages/core/src/plugins/entryModuleLoader.ts @@ -1,30 +1,8 @@ import type { Rspack } from '@rsbuild/core'; -import { REACT_DIRECTIVE_REGEX, SHEBANG_REGEX } from '../constant'; - -function splitFromFirstLine(text: string): [string, string] { - const match = text.match(/(\r\n|\n)/); - if (!match) { - return [text, '']; - } - - return [text.slice(0, match.index), text.slice(match.index)]; -} +// Empty loader, only to make doppelganger of entry module. const loader: Rspack.LoaderDefinition = function loader(source) { - let result = source; - - const [firstLine1, rest] = splitFromFirstLine(result); - - if (SHEBANG_REGEX.test(firstLine1)) { - result = rest; - } - - const [firstLine2, rest2] = splitFromFirstLine(result); - if (REACT_DIRECTIVE_REGEX.test(firstLine2)) { - result = rest2; - } - - return result; + return source; }; export default loader; diff --git a/packages/core/tests/__snapshots__/config.test.ts.snap b/packages/core/tests/__snapshots__/config.test.ts.snap index 92bf44d33..a157d257a 100644 --- a/packages/core/tests/__snapshots__/config.test.ts.snap +++ b/packages/core/tests/__snapshots__/config.test.ts.snap @@ -634,17 +634,6 @@ exports[`Should compose create Rsbuild config correctly > Enable experiment.adva } } ] - }, - /* config.module.rule('Rslib:js-entry-loader') */ - { - test: /\\.(?:js|jsx|mjs|cjs|ts|tsx|mts|cts)$/, - issuer: /^$/, - use: [ - /* config.module.rule('Rslib:js-entry-loader').use('rsbuild:lib-entry-module') */ - { - loader: '/dist/entryModuleLoader.js' - } - ] } ] }, @@ -666,6 +655,7 @@ exports[`Should compose create Rsbuild config correctly > Enable experiment.adva mangle: false, minify: false, compress: { + directives: false, defaults: false, unused: true, dead_code: true, @@ -717,11 +707,7 @@ exports[`Should compose create Rsbuild config correctly > Enable experiment.adva options: undefined }, { - reactDirectives: {}, shimsInjectedAssets: new Set([]), - shebangChmod: 493, - shebangEntries: {}, - shebangInjectedAssets: new Set([]), enabledImportMetaUrlShim: false, contextToWatch: null } @@ -1727,6 +1713,7 @@ exports[`Should compose create Rsbuild config correctly > Merge Rsbuild config i mangle: false, minify: false, compress: { + directives: false, defaults: false, unused: true, dead_code: true, @@ -1773,11 +1760,7 @@ exports[`Should compose create Rsbuild config correctly > Merge Rsbuild config i ] }, { - reactDirectives: {}, shimsInjectedAssets: new Set([]), - shebangChmod: 493, - shebangEntries: {}, - shebangInjectedAssets: new Set([]), enabledImportMetaUrlShim: false, contextToWatch: null } @@ -2474,6 +2457,7 @@ exports[`Should compose create Rsbuild config correctly > Merge Rsbuild config i mangle: false, minify: false, compress: { + directives: false, defaults: false, unused: true, dead_code: true, @@ -2517,11 +2501,7 @@ exports[`Should compose create Rsbuild config correctly > Merge Rsbuild config i ] }, { - reactDirectives: {}, shimsInjectedAssets: new Set([]), - shebangChmod: 493, - shebangEntries: {}, - shebangInjectedAssets: new Set([]), enabledImportMetaUrlShim: true, contextToWatch: null } @@ -3124,6 +3104,7 @@ exports[`Should compose create Rsbuild config correctly > Merge Rsbuild config i mangle: false, minify: false, compress: { + directives: false, defaults: false, unused: true, dead_code: true, @@ -3166,11 +3147,7 @@ exports[`Should compose create Rsbuild config correctly > Merge Rsbuild config i ] }, { - reactDirectives: {}, shimsInjectedAssets: new Set([]), - shebangChmod: 493, - shebangEntries: {}, - shebangInjectedAssets: new Set([]), enabledImportMetaUrlShim: false, contextToWatch: null } @@ -3772,6 +3749,7 @@ exports[`Should compose create Rsbuild config correctly > Merge Rsbuild config i mangle: false, minify: false, compress: { + directives: false, defaults: false, unused: true, dead_code: true, @@ -3814,11 +3792,7 @@ exports[`Should compose create Rsbuild config correctly > Merge Rsbuild config i ] }, { - reactDirectives: {}, shimsInjectedAssets: new Set([]), - shebangChmod: 493, - shebangEntries: {}, - shebangInjectedAssets: new Set([]), enabledImportMetaUrlShim: false, contextToWatch: null } @@ -4371,6 +4345,7 @@ exports[`Should compose create Rsbuild config correctly > Merge Rsbuild config i mangle: false, minify: true, compress: { + directives: false, defaults: false, unused: true, dead_code: true, @@ -4411,11 +4386,7 @@ exports[`Should compose create Rsbuild config correctly > Merge Rsbuild config i } ), { - reactDirectives: {}, shimsInjectedAssets: new Set([]), - shebangChmod: 493, - shebangEntries: {}, - shebangInjectedAssets: new Set([]), enabledImportMetaUrlShim: false, contextToWatch: null } @@ -4524,6 +4495,7 @@ exports[`Should compose create Rsbuild config correctly > Merge Rsbuild config i "compress": { "dead_code": true, "defaults": false, + "directives": false, "toplevel": true, "unused": true, }, @@ -4697,10 +4669,6 @@ exports[`Should compose create Rsbuild config correctly > Merge Rsbuild config i EntryChunkPlugin { "contextToWatch": null, "enabledImportMetaUrlShim": false, - "reactDirectives": {}, - "shebangChmod": 493, - "shebangEntries": {}, - "shebangInjectedAssets": Set {}, "shimsInjectedAssets": Set {}, }, ], @@ -4817,6 +4785,7 @@ exports[`Should compose create Rsbuild config correctly > Merge Rsbuild config i "compress": { "dead_code": true, "defaults": false, + "directives": false, "toplevel": true, "unused": true, }, @@ -4987,10 +4956,6 @@ exports[`Should compose create Rsbuild config correctly > Merge Rsbuild config i EntryChunkPlugin { "contextToWatch": null, "enabledImportMetaUrlShim": true, - "reactDirectives": {}, - "shebangChmod": 493, - "shebangEntries": {}, - "shebangInjectedAssets": Set {}, "shimsInjectedAssets": Set {}, }, ], @@ -5097,6 +5062,7 @@ exports[`Should compose create Rsbuild config correctly > Merge Rsbuild config i "compress": { "dead_code": true, "defaults": false, + "directives": false, "toplevel": true, "unused": true, }, @@ -5238,10 +5204,6 @@ exports[`Should compose create Rsbuild config correctly > Merge Rsbuild config i EntryChunkPlugin { "contextToWatch": null, "enabledImportMetaUrlShim": false, - "reactDirectives": {}, - "shebangChmod": 493, - "shebangEntries": {}, - "shebangInjectedAssets": Set {}, "shimsInjectedAssets": Set {}, }, ], @@ -5348,6 +5310,7 @@ exports[`Should compose create Rsbuild config correctly > Merge Rsbuild config i "compress": { "dead_code": true, "defaults": false, + "directives": false, "toplevel": true, "unused": true, }, @@ -5491,10 +5454,6 @@ exports[`Should compose create Rsbuild config correctly > Merge Rsbuild config i EntryChunkPlugin { "contextToWatch": null, "enabledImportMetaUrlShim": false, - "reactDirectives": {}, - "shebangChmod": 493, - "shebangEntries": {}, - "shebangInjectedAssets": Set {}, "shimsInjectedAssets": Set {}, }, ], @@ -5543,6 +5502,7 @@ exports[`Should compose create Rsbuild config correctly > Merge Rsbuild config i "compress": { "dead_code": true, "defaults": false, + "directives": false, "toplevel": false, "unused": true, }, @@ -5653,10 +5613,6 @@ exports[`Should compose create Rsbuild config correctly > Merge Rsbuild config i EntryChunkPlugin { "contextToWatch": null, "enabledImportMetaUrlShim": false, - "reactDirectives": {}, - "shebangChmod": 493, - "shebangEntries": {}, - "shebangInjectedAssets": Set {}, "shimsInjectedAssets": Set {}, }, ], diff --git a/packages/core/tests/config.test.ts b/packages/core/tests/config.test.ts index b86723768..36d152e07 100644 --- a/packages/core/tests/config.test.ts +++ b/packages/core/tests/config.test.ts @@ -484,6 +484,7 @@ describe('minify', () => { "compress": { "dead_code": true, "defaults": false, + "directives": false, "toplevel": true, "unused": true, }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b3b4ad813..e04600416 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2895,8 +2895,8 @@ packages: '@prefresh/core': ^1.5.0 '@prefresh/utils': ^1.2.0 - '@rspack/plugin-react-refresh@1.5.2': - resolution: {integrity: sha512-uTbN6P01LPdQOnl5YNwHkN4hDsb9Sb5nIetQb55mPyFiJnu9MQetmBUm+tmh8JJg0QPv4Ew7tXgi4hjpHFY3Rw==} + '@rspack/plugin-react-refresh@1.5.3': + resolution: {integrity: sha512-VOnQMf3YOHkTqJ0+BJbrYga4tQAWNwoAnkgwRauXB4HOyCc5wLfBs9DcOFla/2usnRT3Sq6CMVhXmdPobwAoTA==} peerDependencies: react-refresh: '>=0.10.0 <1.0.0' webpack-hot-middleware: 2.x @@ -9108,7 +9108,7 @@ snapshots: '@rsbuild/plugin-react@1.4.2(@rsbuild/core@1.6.7)': dependencies: '@rsbuild/core': 1.6.7 - '@rspack/plugin-react-refresh': 1.5.2(react-refresh@0.18.0) + '@rspack/plugin-react-refresh': 1.5.3(react-refresh@0.18.0) react-refresh: 0.18.0 transitivePeerDependencies: - webpack-hot-middleware @@ -9337,7 +9337,7 @@ snapshots: '@prefresh/core': 1.5.8(preact@10.27.2) '@prefresh/utils': 1.2.1 - '@rspack/plugin-react-refresh@1.5.2(react-refresh@0.18.0)': + '@rspack/plugin-react-refresh@1.5.3(react-refresh@0.18.0)': dependencies: error-stack-parser: 2.1.4 html-entities: 2.6.0 diff --git a/tests/integration/directive/index.test.ts b/tests/integration/directive/index.test.ts index 31525fc71..9d494c8ec 100644 --- a/tests/integration/directive/index.test.ts +++ b/tests/integration/directive/index.test.ts @@ -59,7 +59,7 @@ describe('shebang', async () => { expect(foo!.includes('#!')).toBe(false); }); - test.todo('shebang commented by JS parser should be striped', async () => { + test('shebang commented by JS parser should be striped', async () => { const { content: index } = queryContent(contents.esm3!, 'index.js', { basename: true, }); @@ -98,24 +98,24 @@ describe('react', async () => { const { content: foo } = queryContent(contents.esm!, 'foo.js', { basename: true, }); - expect(onlyStartsWith(foo!, `'use client';`)).toBe(true); + expect(onlyStartsWith(foo!, `"use client";`)).toBe(true); const { content: bar } = queryContent(contents.esm!, 'bar.js', { basename: true, }); - expect(onlyStartsWith(bar!, `'use server';`)).toBe(true); + expect(onlyStartsWith(bar!, `"use server";`)).toBe(true); }); test('React directive at the beginning even if minified', async () => { const { content: foo } = queryContent(contents.cjs!, 'foo.cjs', { basename: true, }); - expect(onlyStartsWith(foo!, `'use client';`)).toBe(true); + expect(onlyStartsWith(foo!, `"use strict";"use client";`)).toBe(true); const { content: bar } = queryContent(contents.cjs!, 'bar.cjs', { basename: true, }); - expect(onlyStartsWith(bar!, `'use server';`)).toBe(true); + expect(onlyStartsWith(bar!, `"use strict";"use server";`)).toBe(true); }); }); }); diff --git a/tests/integration/directive/react/bundleless/rslib.config.ts b/tests/integration/directive/react/bundleless/rslib.config.ts index 435d9f892..e48071ada 100644 --- a/tests/integration/directive/react/bundleless/rslib.config.ts +++ b/tests/integration/directive/react/bundleless/rslib.config.ts @@ -12,7 +12,16 @@ export default defineConfig({ generateBundleCjsConfig({ bundle: false, output: { - minify: true, + minify: { + jsOptions: { + minimizerOptions: { + compress: { + // `directives` option is required to keep `"use strict"` in the output files. + directives: false, + }, + }, + }, + }, distPath: './dist/cjs', }, }), diff --git a/tests/integration/entry/index.test.ts b/tests/integration/entry/index.test.ts index 2cd9cfbf0..32b1cedf0 100644 --- a/tests/integration/entry/index.test.ts +++ b/tests/integration/entry/index.test.ts @@ -56,6 +56,7 @@ test('multiple entry bundle', async () => { "/tests/integration/entry/multiple/dist/cjs/shared.cjs", ], "esm": [ + "/tests/integration/entry/multiple/dist/esm/447.js", "/tests/integration/entry/multiple/dist/esm/994.js", "/tests/integration/entry/multiple/dist/esm/foo.js", "/tests/integration/entry/multiple/dist/esm/index.js", @@ -86,8 +87,8 @@ test('multiple entry bundle', async () => { // cspell:disable if (process.env.ADVANCED_ESM) { expect(index).toMatchInlineSnapshot(` - "import { shared } from "./994.js"; - const foo = shared('foo'); + "import { foo } from "./447.js"; + import { shared } from "./994.js"; const src_text = ()=>\`\${foo} \${shared('index')}\`; export { src_text as text }; " @@ -109,9 +110,7 @@ test('multiple entry bundle', async () => { // cspell:disable if (process.env.ADVANCED_ESM) { expect(foo).toMatchInlineSnapshot(` - "import { shared } from "./994.js"; - const foo = shared('foo'); - export { foo }; + "export { foo } from "./447.js"; " `); } else { @@ -127,11 +126,19 @@ test('multiple entry bundle', async () => { const { content: shared } = queryContent(contents.esm, 'shared.js', { basename: true, }); - expect(shared).toMatchInlineSnapshot(` - "const shared = (str)=>'shared-' + str; - export { shared }; - " - `); + + if (process.env.ADVANCED_ESM) { + expect(shared).toMatchInlineSnapshot(` + "export { shared } from "./994.js"; + " + `); + } else { + expect(shared).toMatchInlineSnapshot(` + "const shared = (str)=>'shared-' + str; + export { shared }; + " + `); + } }); test('glob entry bundleless', async () => { diff --git a/tests/integration/shims/index.test.ts b/tests/integration/shims/index.test.ts index 31bceb02e..2aafeed90 100644 --- a/tests/integration/shims/index.test.ts +++ b/tests/integration/shims/index.test.ts @@ -127,7 +127,7 @@ describe('CJS shims', () => { const dynamicUrl = await dynamicImportMetaUrl(); const { content: dynamicContent } = queryContent( contents.cjs!, - /cjs\/1~368\.cjs/, + /1~\d+\.cjs/, ); expect(importMetaUrl).toBe(fileUrl); diff --git a/website/docs/en/config/rsbuild/output.mdx b/website/docs/en/config/rsbuild/output.mdx index 9c4a6fe68..73b0b294a 100644 --- a/website/docs/en/config/rsbuild/output.mdx +++ b/website/docs/en/config/rsbuild/output.mdx @@ -149,6 +149,7 @@ export default defineConfig({ minify: false, compress: { defaults: false, + directives: false, unused: true, dead_code: true, toplevel: true, @@ -189,6 +190,7 @@ export default defineConfig({ minify: true, compress: { defaults: false, + directives: false, unused: true, dead_code: true, // Avoid remoteEntry's global variable being tree-shaken diff --git a/website/docs/zh/config/rsbuild/output.mdx b/website/docs/zh/config/rsbuild/output.mdx index 959cc3824..3473e6f68 100644 --- a/website/docs/zh/config/rsbuild/output.mdx +++ b/website/docs/zh/config/rsbuild/output.mdx @@ -145,6 +145,7 @@ export default defineConfig({ minify: false, compress: { defaults: false, + directives: false, unused: true, dead_code: true, toplevel: true, @@ -185,6 +186,7 @@ export default defineConfig({ minify: true, compress: { defaults: false, + directives: false, unused: true, dead_code: true, // 避免 remoteEntry 的全局变量被 tree-shaking