diff --git a/.changeset/sharp-elephants-build.md b/.changeset/sharp-elephants-build.md new file mode 100644 index 00000000..de074491 --- /dev/null +++ b/.changeset/sharp-elephants-build.md @@ -0,0 +1,5 @@ +--- +'@rsdoctor/rspack-plugin': patch +--- + +feat(loader): support for the loaders analysis with vue-loader diff --git a/packages/core/src/build-utils/build/index.ts b/packages/core/src/build-utils/build/index.ts index b11c25ea..fe69ec46 100644 --- a/packages/core/src/build-utils/build/index.ts +++ b/packages/core/src/build-utils/build/index.ts @@ -1,4 +1,5 @@ export * as Chunks from './chunks'; export * as Utils from './utils'; +export * as Loader from './loader'; export * as Types from '../../types'; export * as ModuleGraph from './module-graph'; diff --git a/packages/core/src/build-utils/build/loader/index.ts b/packages/core/src/build-utils/build/loader/index.ts new file mode 100644 index 00000000..05c1d2a8 --- /dev/null +++ b/packages/core/src/build-utils/build/loader/index.ts @@ -0,0 +1 @@ +export * from './probeLoaderPlugin'; diff --git a/packages/rspack-plugin/src/probeLoader.ts b/packages/core/src/build-utils/build/loader/probeLoader.ts similarity index 91% rename from packages/rspack-plugin/src/probeLoader.ts rename to packages/core/src/build-utils/build/loader/probeLoader.ts index be181653..82b659e1 100644 --- a/packages/rspack-plugin/src/probeLoader.ts +++ b/packages/core/src/build-utils/build/loader/probeLoader.ts @@ -1,11 +1,11 @@ -import { getSDK } from '@rsdoctor/core/plugins'; +import { Build } from '@/build-utils'; +import { getSDK } from '@/inner-plugins'; import { Plugin, SDK } from '@rsdoctor/types'; -import { Build } from '@rsdoctor/core'; import type { LoaderDefinitionFunction } from '@rspack/core'; import { omit } from 'lodash'; import path from 'path'; -const loaderModule: Plugin.LoaderDefinition< +export const loaderModule: Plugin.LoaderDefinition< Parameters, {} > = function (...args) { diff --git a/packages/core/src/build-utils/build/loader/probeLoaderPlugin.ts b/packages/core/src/build-utils/build/loader/probeLoaderPlugin.ts new file mode 100644 index 00000000..4fa145e7 --- /dev/null +++ b/packages/core/src/build-utils/build/loader/probeLoaderPlugin.ts @@ -0,0 +1,61 @@ +import { Plugin } from '@rsdoctor/types'; +import type { RuleSetRules } from '@rspack/core'; +import { Loader } from '@rsdoctor/utils/common'; +import { Build } from '@/build-utils'; +import { Utils } from '..'; + +const BuiltinLoaderName = 'builtin:swc-loader'; +const ESMLoaderFile = '.mjs'; + +export class ProbeLoaderPlugin { + apply(compiler: Plugin.BaseCompiler) { + compiler.hooks.beforeRun.tap( + { + name: 'ProbeLoaderPlugin', + }, + () => { + this.addProbeLoader(compiler); + }, + ); + + compiler.hooks.watchRun.tap( + { + name: 'ProbeLoaderPlugin', + }, + () => { + this.addProbeLoader(compiler); + }, + ); + } + + private addProbeLoader(compiler: Plugin.BaseCompiler) { + let rules = compiler.options.module.rules as Plugin.RuleSetRule[]; + + if (Loader.isVue(compiler)) { + compiler.options.module.rules = Utils.addProbeLoader2Rules( + rules, + compiler, + (r: Plugin.BuildRuleSetRule) => !!r.loader || typeof r === 'string', + ) as RuleSetRules; + return; + } + + rules = Utils.addProbeLoader2Rules( + rules, + compiler, + (r: Plugin.BuildRuleSetRule) => + Build.Utils.getLoaderNameMatch(r, BuiltinLoaderName, true), + ) as Plugin.RuleSetRule[]; + + compiler.options.module.rules = Utils.addProbeLoader2Rules( + rules, + compiler, + (r: Plugin.BuildRuleSetRule) => { + return ( + Build.Utils.getLoaderNameMatch(r, ESMLoaderFile, false) || + Build.Utils.isESMLoader(r) + ); + }, + ) as RuleSetRules; + } +} diff --git a/packages/core/src/build-utils/build/utils/index.ts b/packages/core/src/build-utils/build/utils/index.ts index b326862f..870c8e52 100644 --- a/packages/core/src/build-utils/build/utils/index.ts +++ b/packages/core/src/build-utils/build/utils/index.ts @@ -1,3 +1,4 @@ export * from './loader'; export * from './plugin'; export * from './parseBundle'; +export * from '../loader/probeLoader'; diff --git a/packages/core/src/build-utils/build/utils/loader.ts b/packages/core/src/build-utils/build/utils/loader.ts index 36d924d0..cda05434 100644 --- a/packages/core/src/build-utils/build/utils/loader.ts +++ b/packages/core/src/build-utils/build/utils/loader.ts @@ -1,10 +1,13 @@ import path from 'path'; import fse from 'fs-extra'; -import { omit, findIndex } from 'lodash'; +import { omit } from 'lodash'; import { Loader } from '@rsdoctor/utils/common'; import type { Common, Plugin } from '@rsdoctor/types'; import { Rule, SourceMapInput as WebpackSourceMapInput } from '../../../types'; import { readPackageJson } from '@rsdoctor/graph'; +import { RuleSetUseItem } from '@rspack/core'; +import { RuleSetUseItem as WebpackRuleSetUseItem } from 'webpack'; +import { debug } from '@rsdoctor/utils/logger'; // webpack https://github.com/webpack/webpack/blob/2953d23a87d89b3bd07cf73336ee34731196c62e/lib/util/identifier.js#L311 // rspack https://github.com/web-infra-dev/rspack/blob/d22f049d4bce4f8ac20c1cbabeab3706eddaecc1/packages/rspack/src/loader-runner/index.ts#L47 @@ -90,8 +93,8 @@ export function mapEachRules( } // https://webpack.js.org/configuration/module/#ruleloaders - if (Array.isArray((rule as unknown as Rule).loaders)) { - const { loaders, ...rest } = rule as unknown as Rule; + if (Array.isArray((rule as Rule).loaders)) { + const { loaders, ...rest } = rule as Rule; return { ...(rest as Plugin.RuleSetRule), use: mapEachRules(loaders as T[], callback), @@ -119,7 +122,7 @@ export function mapEachRules( const newRule = { ...rule, use: (...args: any) => { - const rules = funcUse.apply(null, args) as any; + const rules = funcUse.apply(null, args) as T[]; return mapEachRules(rules, callback); }, }; @@ -176,7 +179,7 @@ export function isESMLoader(r: Plugin.BuildRuleSetRule) { try { return fse.readJsonSync(file, { encoding: 'utf8' }); } catch (e) { - // console.log(e) + debug(() => `isESMLoader function error:${e}`); } }); @@ -187,6 +190,40 @@ export function isESMLoader(r: Plugin.BuildRuleSetRule) { return false; } +function appendProbeLoaders( + compiler: Plugin.BaseCompiler, + loaderConfig: RuleSetUseItem | WebpackRuleSetUseItem, +): RuleSetUseItem[] { + const _options = + typeof loaderConfig === 'object' + ? typeof loaderConfig.options === 'string' + ? { options: loaderConfig.options } + : loaderConfig.options + : {}; + const loaderPath = path.join(__dirname, '../loader/probeLoader.js'); + const loader = + typeof loaderConfig === 'string' + ? loaderConfig + : typeof loaderConfig === 'object' && loaderConfig.loader; + + const createProbeLoader = (type: 'start' | 'end') => ({ + loader: loaderPath, + options: { + ..._options, + loader, + ident: undefined, + type, + builderName: compiler.options.name, + }, + }); + + return [ + createProbeLoader('end'), + loaderConfig as RuleSetUseItem, + createProbeLoader('start'), + ]; +} + export function getLoaderNameMatch( r: Plugin.BuildRuleSetRule, loaderName: string, @@ -200,7 +237,6 @@ export function getLoaderNameMatch( (typeof r === 'string' && (r as string).includes(loaderName)) ); } - return ( (typeof r === 'object' && typeof r?.loader === 'string' && @@ -208,64 +244,84 @@ export function getLoaderNameMatch( (typeof r === 'string' && r === loaderName) ); } - -// FIXME: Type BuildRuleSetRule maybe need optimize. export function addProbeLoader2Rules( rules: T[], - appendRules: (rule: T, index: number) => T, + compiler: Plugin.BaseCompiler, fn: (r: Plugin.BuildRuleSetRule) => boolean, ): T[] { return rules.map((rule) => { if (!rule || typeof rule === 'string') return rule; - if (fn(rule)) { - const _rule = { + // Handle single loader case + if (fn(rule) && !rule.use) { + const loaderConfig: RuleSetUseItem = { + loader: rule.loader ?? '', + options: rule.options, + ident: + 'ident' in rule && typeof rule.ident === 'string' + ? rule.ident + : undefined, + }; + return { ...rule, - use: [ - { - loader: rule.loader, - options: rule.options, - }, - ], + use: appendProbeLoaders(compiler, loaderConfig), loader: undefined, options: undefined, }; - return appendRules(_rule, 0); } + // Handle 'use' property if (rule.use) { if (Array.isArray(rule.use)) { - const _index = findIndex(rule.use, (_r) => fn(_r as T)); - if (_index > -1) { - return appendRules(rule, _index); - } + rule.use = rule.use.flatMap((loaderConfig) => { + if ( + typeof loaderConfig === 'string' || + (typeof loaderConfig === 'object' && + loaderConfig && + 'loader' in loaderConfig) + ) { + return fn(loaderConfig as T) + ? appendProbeLoaders(compiler, loaderConfig) + : [loaderConfig]; + } + return [loaderConfig]; + }); } else if ( typeof rule.use === 'object' && !Array.isArray(rule.use) && typeof rule.use !== 'function' ) { - rule.use = [ - { - ...rule.use, - }, - ]; - return appendRules(rule, 0); + if ('loader' in rule.use) { + rule.use = fn(rule.use as T) + ? appendProbeLoaders(compiler, rule.use) + : [rule.use]; + } + } else if (typeof rule.use === 'string') { + rule.use = fn(rule.use as unknown as T) + ? appendProbeLoaders(compiler, { loader: rule.use }) + : [ + { + loader: rule.use, + }, + ]; } } + // Handle nested rules if ('oneOf' in rule && rule.oneOf) { return { ...rule, - oneOf: addProbeLoader2Rules(rule.oneOf as T[], appendRules, fn), + oneOf: addProbeLoader2Rules(rule.oneOf as T[], compiler, fn), }; } if ('rules' in rule && rule.rules) { return { ...rule, - rules: addProbeLoader2Rules(rule.rules as T[], appendRules, fn), + rules: addProbeLoader2Rules(rule.rules as T[], compiler, fn), }; } + return rule; }); } diff --git a/packages/core/tests/build/utils/loader/__snapshots__/changeBuiltinLoader.test.ts.snap b/packages/core/tests/build/utils/loader/__snapshots__/changeBuiltinLoader.test.ts.snap index fc189ce1..6e6772b3 100644 --- a/packages/core/tests/build/utils/loader/__snapshots__/changeBuiltinLoader.test.ts.snap +++ b/packages/core/tests/build/utils/loader/__snapshots__/changeBuiltinLoader.test.ts.snap @@ -5,16 +5,28 @@ exports[`test src/build/utils/loader.ts addProbeLoader2Rules > addProbeLoader2Ru { "test": /\\\\\\.less\\$/, "type": "css", - "use": "less-loader", + "use": [ + { + "loader": "less-loader", + }, + ], }, { "test": /\\\\\\.module\\\\\\.less\\$/, "type": "css/module", - "use": "less-loader", + "use": [ + { + "loader": "less-loader", + }, + ], }, { "test": /\\\\\\.svg\\$/, - "use": "@svgr/webpack", + "use": [ + { + "loader": "@svgr/webpack", + }, + ], }, { "oneOf": [ @@ -24,22 +36,23 @@ exports[`test src/build/utils/loader.ts addProbeLoader2Rules > addProbeLoader2Ru "type": "javascript/auto", "use": [ { - "loader": "probe", + "loader": "/packages/core/src/build-utils/build/loader/probeLoader.js", "options": { - "loader": "builtin:swc-loader", - "options": { - "jsc": { - "parser": { - "jsx": true, - "syntax": "typescript", - }, + "builderName": "test-compiler", + "ident": undefined, + "jsc": { + "parser": { + "jsx": true, + "syntax": "typescript", }, - "sourceMap": true, }, + "loader": "builtin:swc-loader", + "sourceMap": true, "type": "end", }, }, { + "ident": undefined, "loader": "builtin:swc-loader", "options": { "jsc": { @@ -52,18 +65,18 @@ exports[`test src/build/utils/loader.ts addProbeLoader2Rules > addProbeLoader2Ru }, }, { - "loader": "probe", + "loader": "/packages/core/src/build-utils/build/loader/probeLoader.js", "options": { - "loader": "builtin:swc-loader", - "options": { - "jsc": { - "parser": { - "jsx": true, - "syntax": "typescript", - }, + "builderName": "test-compiler", + "ident": undefined, + "jsc": { + "parser": { + "jsx": true, + "syntax": "typescript", }, - "sourceMap": true, }, + "loader": "builtin:swc-loader", + "sourceMap": true, "type": "start", }, }, @@ -80,22 +93,23 @@ exports[`test src/build/utils/loader.ts addProbeLoader2Rules > addProbeLoader2Ru "type": "javascript/auto", "use": [ { - "loader": "probe", + "loader": "/packages/core/src/build-utils/build/loader/probeLoader.js", "options": { - "loader": "builtin:swc-loader", - "options": { - "jsc": { - "parser": { - "jsx": true, - "syntax": "typescript", - }, + "builderName": "test-compiler", + "ident": undefined, + "jsc": { + "parser": { + "jsx": true, + "syntax": "typescript", }, - "sourceMap": true, }, + "loader": "builtin:swc-loader", + "sourceMap": true, "type": "end", }, }, { + "ident": undefined, "loader": "builtin:swc-loader", "options": { "jsc": { @@ -108,18 +122,18 @@ exports[`test src/build/utils/loader.ts addProbeLoader2Rules > addProbeLoader2Ru }, }, { - "loader": "probe", + "loader": "/packages/core/src/build-utils/build/loader/probeLoader.js", "options": { - "loader": "builtin:swc-loader", - "options": { - "jsc": { - "parser": { - "jsx": true, - "syntax": "typescript", - }, + "builderName": "test-compiler", + "ident": undefined, + "jsc": { + "parser": { + "jsx": true, + "syntax": "typescript", }, - "sourceMap": true, }, + "loader": "builtin:swc-loader", + "sourceMap": true, "type": "start", }, }, @@ -133,19 +147,19 @@ exports[`test src/build/utils/loader.ts addProbeLoader2Rules > addProbeLoader2Ru "type": "javascript/auto", "use": [ { - "loader": "probe", + "loader": "/packages/core/src/build-utils/build/loader/probeLoader.js", "options": { - "loader": "builtin:swc-loader", - "options": { - "jsc": { - "externalHelpers": true, - "parser": { - "syntax": "typescript", - }, - "preserveAllComments": false, + "builderName": "test-compiler", + "ident": undefined, + "jsc": { + "externalHelpers": true, + "parser": { + "syntax": "typescript", }, - "sourceMap": true, + "preserveAllComments": false, }, + "loader": "builtin:swc-loader", + "sourceMap": true, "type": "end", }, }, @@ -163,19 +177,19 @@ exports[`test src/build/utils/loader.ts addProbeLoader2Rules > addProbeLoader2Ru }, }, { - "loader": "probe", + "loader": "/packages/core/src/build-utils/build/loader/probeLoader.js", "options": { - "loader": "builtin:swc-loader", - "options": { - "jsc": { - "externalHelpers": true, - "parser": { - "syntax": "typescript", - }, - "preserveAllComments": false, + "builderName": "test-compiler", + "ident": undefined, + "jsc": { + "externalHelpers": true, + "parser": { + "syntax": "typescript", }, - "sourceMap": true, + "preserveAllComments": false, }, + "loader": "builtin:swc-loader", + "sourceMap": true, "type": "start", }, }, diff --git a/packages/core/tests/build/utils/loader/__snapshots__/changeBuiltinLoaderVue.test.ts.snap b/packages/core/tests/build/utils/loader/__snapshots__/changeBuiltinLoaderVue.test.ts.snap new file mode 100644 index 00000000..07bb0e50 --- /dev/null +++ b/packages/core/tests/build/utils/loader/__snapshots__/changeBuiltinLoaderVue.test.ts.snap @@ -0,0 +1,205 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`test addProbeLoader2Rules for vue-loader > addProbeLoader2Rules() 1`] = ` +[ + { + "test": /\\\\\\.less\\$/, + "type": "css", + "use": [ + { + "loader": "/packages/core/src/build-utils/build/loader/probeLoader.js", + "options": { + "builderName": "test-compiler", + "ident": undefined, + "loader": "less-loader", + "type": "end", + }, + }, + { + "loader": "less-loader", + }, + { + "loader": "/packages/core/src/build-utils/build/loader/probeLoader.js", + "options": { + "builderName": "test-compiler", + "ident": undefined, + "loader": "less-loader", + "type": "start", + }, + }, + ], + }, + { + "test": /\\\\\\.module\\\\\\.less\\$/, + "type": "css/module", + "use": [ + { + "loader": "/packages/core/src/build-utils/build/loader/probeLoader.js", + "options": { + "builderName": "test-compiler", + "ident": undefined, + "loader": "less-loader", + "type": "end", + }, + }, + { + "loader": "less-loader", + }, + { + "loader": "/packages/core/src/build-utils/build/loader/probeLoader.js", + "options": { + "builderName": "test-compiler", + "ident": undefined, + "loader": "less-loader", + "type": "start", + }, + }, + ], + }, + { + "test": /\\\\\\.svg\\$/, + "use": [ + { + "loader": "/packages/core/src/build-utils/build/loader/probeLoader.js", + "options": { + "builderName": "test-compiler", + "ident": undefined, + "loader": "@svgr/webpack", + "type": "end", + }, + }, + { + "loader": "@svgr/webpack", + }, + { + "loader": "/packages/core/src/build-utils/build/loader/probeLoader.js", + "options": { + "builderName": "test-compiler", + "ident": undefined, + "loader": "@svgr/webpack", + "type": "start", + }, + }, + ], + }, + { + "rules": [ + { + "experimentalInlineMatchResource": true, + "loader": undefined, + "options": undefined, + "use": [ + { + "loader": "/packages/core/src/build-utils/build/loader/probeLoader.js", + "options": { + "builderName": "test-compiler", + "ident": undefined, + "loader": "vue-loader", + "type": "end", + }, + }, + { + "ident": undefined, + "loader": "vue-loader", + "options": undefined, + }, + { + "loader": "/packages/core/src/build-utils/build/loader/probeLoader.js", + "options": { + "builderName": "test-compiler", + "ident": undefined, + "loader": "vue-loader", + "type": "start", + }, + }, + ], + }, + ], + "test": /\\\\\\.vue\\$/, + }, + { + "test": /\\\\\\.ts\\$/, + "type": "javascript/auto", + "use": [ + { + "loader": "/packages/core/src/build-utils/build/loader/probeLoader.js", + "options": { + "builderName": "test-compiler", + "ident": undefined, + "jsc": { + "externalHelpers": true, + "parser": { + "syntax": "typescript", + }, + "preserveAllComments": false, + }, + "loader": "builtin:swc-loader", + "sourceMap": true, + "type": "end", + }, + }, + { + "loader": "builtin:swc-loader", + "options": { + "jsc": { + "externalHelpers": true, + "parser": { + "syntax": "typescript", + }, + "preserveAllComments": false, + }, + "sourceMap": true, + }, + }, + { + "loader": "/packages/core/src/build-utils/build/loader/probeLoader.js", + "options": { + "builderName": "test-compiler", + "ident": undefined, + "jsc": { + "externalHelpers": true, + "parser": { + "syntax": "typescript", + }, + "preserveAllComments": false, + }, + "loader": "builtin:swc-loader", + "sourceMap": true, + "type": "start", + }, + }, + ], + }, + { + "loader": undefined, + "options": undefined, + "test": /\\\\\\.svg\\$/, + "type": "asset/resource", + "use": [ + { + "loader": "/packages/core/src/build-utils/build/loader/probeLoader.js", + "options": { + "builderName": "test-compiler", + "ident": undefined, + "loader": "svg-loader", + "type": "end", + }, + }, + { + "ident": undefined, + "loader": "svg-loader", + "options": undefined, + }, + { + "loader": "/packages/core/src/build-utils/build/loader/probeLoader.js", + "options": { + "builderName": "test-compiler", + "ident": undefined, + "loader": "svg-loader", + "type": "start", + }, + }, + ], + }, +] +`; diff --git a/packages/core/tests/build/utils/loader/changeBuiltinLoader.test.ts b/packages/core/tests/build/utils/loader/changeBuiltinLoader.test.ts index 2ffd3a0c..b005f1d3 100644 --- a/packages/core/tests/build/utils/loader/changeBuiltinLoader.test.ts +++ b/packages/core/tests/build/utils/loader/changeBuiltinLoader.test.ts @@ -1,7 +1,8 @@ -import { addProbeLoader2Rules } from '@/build-utils/build/utils/loader'; import { describe, it, expect } from 'vitest'; +import os from 'os'; import { Plugin } from '@rsdoctor/types'; import { Utils } from '@/build-utils/build'; +import { addProbeLoader2Rules } from '@/build-utils/build/utils'; const rules = [ { @@ -77,28 +78,73 @@ const rules = [ }, ]; -const appendRule = (rule: Plugin.RuleSetRule, index: number) => { - if ('use' in rule && Array.isArray(rule.use)) { - const _builtinRule = rule.use[index] as Plugin.RuleSetRule; - const _options = - typeof _builtinRule.options === 'string' ? {} : { ..._builtinRule }; - rule.use.splice(index, 0, { - loader: 'probe', - options: { ..._options, type: 'end' }, - }); - rule.use.splice(index + 2, 0, { - loader: 'probe', - options: { ..._options, type: 'start' }, - }); - } - return rule; -}; +const mockCompiler: Plugin.BaseCompiler = { + options: { + name: 'test-compiler', + }, +} as Plugin.BaseCompiler; + describe('test src/build/utils/loader.ts addProbeLoader2Rules', () => { it('addProbeLoader2Rules()', () => { expect( - addProbeLoader2Rules(rules, appendRule, (r: Plugin.BuildRuleSetRule) => + addProbeLoader2Rules(rules, mockCompiler, (r: Plugin.BuildRuleSetRule) => Utils.getLoaderNameMatch(r, 'builtin:swc-loader', true), ), ).toMatchSnapshot(); }); }); + +describe('addProbeLoader2Rules', () => { + const mockCompiler: Plugin.BaseCompiler = { + options: { + name: 'test-compiler', + }, + } as Plugin.BaseCompiler; + + const mockRule = { + loader: 'mock-loader', + options: { foo: 'bar' }, + } as { + loader: string; + options: any; + use?: any; + }; + + const mockFn = (rule: Plugin.BuildRuleSetRule) => + rule.loader === 'mock-loader'; + + it('should add probe loaders to rules', () => { + const rules = [mockRule]; + const result = addProbeLoader2Rules(rules, mockCompiler, mockFn); + const porbeLoaderPath = + os.EOL === '\n' ? 'build-utils/build/loader/probeLoader' : 'probeLoader'; + + expect(result).toHaveLength(1); + expect(result[0]).toHaveProperty('use'); + expect(result[0].use).toHaveLength(3); + expect(result[0].use[0]).toHaveProperty('loader'); + expect(result[0].use[0].loader).toContain(porbeLoaderPath); + console.log('result[0].use[0].loader::', result[0].use[0].loader); + expect(result[0].use[0]).toHaveProperty('options.type', 'end'); + expect(result[0].use[1]).toHaveProperty('loader', 'mock-loader'); + expect(result[0].use[2]).toHaveProperty('loader'); + expect(result[0].use[2].loader).toContain(porbeLoaderPath); + console.log('result[0].use[2].loader::', result[0].use[2].loader); + expect(result[0].use[2]).toHaveProperty('options.type', 'start'); + }); + + it('should handle nested rules', () => { + const nestedRule = { + rules: [mockRule], + }; + const rules = [nestedRule]; + const result = addProbeLoader2Rules(rules, mockCompiler, mockFn); + + expect(result).toHaveLength(1); + expect(result[0]).toHaveProperty('rules'); + expect(result[0].rules).toHaveLength(1); + expect(result[0].rules[0]).toHaveProperty('use'); + // @ts-ignore + expect(result[0].rules[0].use).toHaveLength(3); + }); +}); diff --git a/packages/core/tests/build/utils/loader/changeBuiltinLoaderVue.test.ts b/packages/core/tests/build/utils/loader/changeBuiltinLoaderVue.test.ts new file mode 100644 index 00000000..9cb9f842 --- /dev/null +++ b/packages/core/tests/build/utils/loader/changeBuiltinLoaderVue.test.ts @@ -0,0 +1,123 @@ +import { describe, it, expect } from 'vitest'; +import os from 'os'; +import { Plugin } from '@rsdoctor/types'; +import { addProbeLoader2Rules } from '@/build-utils/build/utils'; + +const rules = [ + { + test: /\.less$/, + use: 'less-loader', + type: 'css', + }, + { + test: /\.module\.less$/, + use: 'less-loader', + type: 'css/module', + }, + { + test: /\.svg$/, + use: '@svgr/webpack', + }, + { + test: /\.vue$/, + rules: [ + { + loader: 'vue-loader', + experimentalInlineMatchResource: true, + }, + ], + }, + { + test: /\.ts$/, + use: { + loader: 'builtin:swc-loader', + options: { + sourceMap: true, + jsc: { + parser: { + syntax: 'typescript', + }, + externalHelpers: true, + preserveAllComments: false, + }, + }, + }, + type: 'javascript/auto', + }, + { + test: /\.svg$/, + loader: 'svg-loader', + type: 'asset/resource', + }, +]; + +const mockCompiler: Plugin.BaseCompiler = { + options: { + name: 'test-compiler', + }, +} as Plugin.BaseCompiler; + +describe('test addProbeLoader2Rules for vue-loader', () => { + it('addProbeLoader2Rules()', () => { + expect( + addProbeLoader2Rules( + rules, + mockCompiler, + (r: Plugin.BuildRuleSetRule) => !!r.loader || typeof r === 'string', + ), + ).toMatchSnapshot(); + }); +}); + +describe('addProbeLoader2Rules', () => { + const mockCompiler: Plugin.BaseCompiler = { + options: { + name: 'test-compiler', + }, + } as Plugin.BaseCompiler; + + const mockRule = { + loader: 'mock-loader', + options: { foo: 'bar' }, + } as { + loader: string; + options: any; + use?: any; + }; + + const mockFn = (rule: Plugin.BuildRuleSetRule) => + rule.loader === 'mock-loader'; + + it('should add probe loaders to rules', () => { + const rules = [mockRule]; + const porbeLoaderPath = + os.EOL === '\n' ? 'build-utils/build/loader/probeLoader' : 'probeLoader'; + const result = addProbeLoader2Rules(rules, mockCompiler, mockFn); + + expect(result).toHaveLength(1); + expect(result[0]).toHaveProperty('use'); + expect(result[0].use).toHaveLength(3); + expect(result[0].use[0]).toHaveProperty('loader'); + expect(result[0].use[0].loader).toContain(porbeLoaderPath); + expect(result[0].use[0]).toHaveProperty('options.type', 'end'); + expect(result[0].use[1]).toHaveProperty('loader', 'mock-loader'); + expect(result[0].use[2]).toHaveProperty('loader'); + expect(result[0].use[2].loader).toContain(porbeLoaderPath); + expect(result[0].use[2]).toHaveProperty('options.type', 'start'); + }); + + it('should handle nested rules', () => { + const nestedRule = { + rules: [mockRule], + }; + const rules = [nestedRule]; + const result = addProbeLoader2Rules(rules, mockCompiler, mockFn); + + expect(result).toHaveLength(1); + expect(result[0]).toHaveProperty('rules'); + expect(result[0].rules).toHaveLength(1); + expect(result[0].rules[0]).toHaveProperty('use'); + // @ts-ignore + expect(result[0].rules[0].use).toHaveLength(3); + }); +}); diff --git a/packages/rspack-plugin/src/plugin.ts b/packages/rspack-plugin/src/plugin.ts index ad294e83..9be6e93c 100644 --- a/packages/rspack-plugin/src/plugin.ts +++ b/packages/rspack-plugin/src/plugin.ts @@ -23,9 +23,11 @@ import type { RsdoctorPluginOptionsNormalized, RsdoctorRspackPluginOptions, } from '@rsdoctor/core'; +import { Loader as BuildUtilLoader } from '@rsdoctor/core/build-utils'; import { Constants, Linter, + Manifest, Manifest as ManifestType, Plugin, SDK, @@ -33,7 +35,7 @@ import { import path from 'path'; import { pluginTapName, pluginTapPostOptions } from './constants'; import { cloneDeep } from 'lodash'; -import { ProbeLoaderPlugin } from './probeLoaderPlugin'; + import { Loader } from '@rsdoctor/utils/common'; import { chalk } from '@rsdoctor/utils/logger'; @@ -118,11 +120,18 @@ export class RsdoctorRspackPlugin compiler, ); - if (this.options.features.loader && !Loader.isVue(compiler)) { - new ProbeLoaderPlugin().apply(compiler); - new InternalLoaderPlugin>(this).apply( - compiler, - ); + if (this.options.features.loader) { + new BuildUtilLoader.ProbeLoaderPlugin().apply(compiler); + // add loader page to client + this.sdk.addClientRoutes([ + Manifest.RsdoctorManifestClientRoutes.WebpackLoaders, + ]); + + if (!Loader.isVue(compiler)) { + new InternalLoaderPlugin>(this).apply( + compiler, + ); + } } if (this.options.features.plugins) { diff --git a/packages/rspack-plugin/src/probeLoaderPlugin.ts b/packages/rspack-plugin/src/probeLoaderPlugin.ts deleted file mode 100644 index 80651587..00000000 --- a/packages/rspack-plugin/src/probeLoaderPlugin.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { Utils } from '@rsdoctor/core/build-utils'; -import path from 'path'; -import { Plugin } from '@rsdoctor/types'; -import type { RuleSetRules } from '@rspack/core'; -import { Build } from '@rsdoctor/core'; - -const BuiltinLoaderName = 'builtin:swc-loader'; -const ESMLoaderFile = '.mjs'; - -export class ProbeLoaderPlugin { - apply(compiler: Plugin.BaseCompiler) { - compiler.hooks.beforeRun.tap( - { - name: 'ProbeLoaderPlugin', - }, - () => { - this.addProbeLoader(compiler); - }, - ); - - compiler.hooks.watchRun.tap( - { - name: 'ProbeLoaderPlugin', - }, - () => { - this.addProbeLoader(compiler); - }, - ); - } - - private addProbeLoader(compiler: Plugin.BaseCompiler) { - const rules = compiler.options.module.rules as Plugin.RuleSetRule[]; - const appendRule = (rule: Plugin.RuleSetRule, index: number) => { - if ('use' in rule && Array.isArray(rule.use)) { - const _builtinRule = rule.use[index] as Plugin.RuleSetRule; - const _options = - typeof _builtinRule.options === 'string' ? {} : { ..._builtinRule }; - - rule.use.splice(index, 0, { - loader: path.join(__dirname, './probeLoader.js'), - options: { - ..._options, - type: 'end', - builderName: compiler.options.name, - }, - }); - - rule.use.splice(index + 2, 0, { - loader: path.join(__dirname, './probeLoader.js'), - options: { - ..._options, - type: 'start', - builderName: compiler.options.name, - }, - }); - } - return rule; - }; - - compiler.options.module.rules = Utils.addProbeLoader2Rules( - rules, - appendRule, - (r: Plugin.BuildRuleSetRule) => - Build.Utils.getLoaderNameMatch(r, BuiltinLoaderName, true), - ) as RuleSetRules; - - compiler.options.module.rules = Utils.addProbeLoader2Rules( - rules, - appendRule, - (r: Plugin.BuildRuleSetRule) => { - return ( - Build.Utils.getLoaderNameMatch(r, ESMLoaderFile, false) || - Build.Utils.isESMLoader(r) - ); - }, - ) as RuleSetRules; - } -} diff --git a/packages/webpack-plugin/src/plugin.ts b/packages/webpack-plugin/src/plugin.ts index 1b6d8fad..971a8109 100644 --- a/packages/webpack-plugin/src/plugin.ts +++ b/packages/webpack-plugin/src/plugin.ts @@ -18,7 +18,7 @@ import type { } from '@rsdoctor/core/types'; import { ChunkGraph, ModuleGraph } from '@rsdoctor/graph'; import { openBrowser, RsdoctorWebpackSDK } from '@rsdoctor/sdk'; -import { Constants, Linter, SDK } from '@rsdoctor/types'; +import { Constants, Linter, Manifest, SDK } from '@rsdoctor/types'; import { Process } from '@rsdoctor/utils/build'; import { chalk, debug } from '@rsdoctor/utils/logger'; import { cloneDeep } from 'lodash'; @@ -29,6 +29,7 @@ import { pluginTapName, pluginTapPostOptions } from './constants'; import { InternalResolverPlugin } from './plugins/resolver'; import { ensureModulesChunksGraphFn } from '@rsdoctor/core/plugins'; import { Loader } from '@rsdoctor/utils/common'; +import { Loader as BuildUtilLoader } from '@rsdoctor/core/build-utils'; export class RsdoctorWebpackPlugin implements RsdoctorPluginInstance @@ -92,8 +93,16 @@ export class RsdoctorWebpackPlugin // @ts-ignore new InternalSummaryPlugin(this).apply(compiler); - if (this.options.features.loader && !Loader.isVue(compiler)) { - new InternalLoaderPlugin(this).apply(compiler); + if (this.options.features.loader) { + new BuildUtilLoader.ProbeLoaderPlugin().apply(compiler); + // add loader page to client + this.sdk.addClientRoutes([ + Manifest.RsdoctorManifestClientRoutes.WebpackLoaders, + ]); + + if (!Loader.isVue(compiler)) { + new InternalLoaderPlugin(this).apply(compiler); + } } if (this.options.features.resolver) {