@shuvi/platform-web 1.0.62 → 2.0.0-dev.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,498 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const shared_1 = require("../../../../../shared");
4
+ const webpack_1 = require("@shuvi/toolpack/lib/webpack");
5
+ const { RawSource } = webpack_1.sources;
6
+ // TODO: Rspack 需要确认 ModuleId 类型定义
7
+ // 当前 Rspack 的模块 ID 类型可能与 webpack 不同
8
+ // 需要根据 Rspack 的实际 API 来定义正确的类型
9
+ // type ModuleId = string | number;
10
+ /**
11
+ * Default configuration options for RspackBuildManifestPlugin
12
+ */
13
+ const defaultOptions = {
14
+ filename: 'build-manifest.json',
15
+ modules: false,
16
+ chunkRequest: false
17
+ };
18
+ /**
19
+ * Extracts file extension from a filepath
20
+ * @param filepath - The file path to extract extension from
21
+ * @returns The file extension (without dot) or empty string if no extension
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * getFileExt('app.js') // returns 'js'
26
+ * getFileExt('styles.css') // returns 'css'
27
+ * getFileExt('README') // returns ''
28
+ * ```
29
+ */
30
+ function getFileExt(filepath) {
31
+ const match = filepath.match(/\.(\w+)$/);
32
+ if (!match)
33
+ return '';
34
+ return match[1];
35
+ }
36
+ /**
37
+ * Rspack plugin that generates a build manifest JSON file containing mappings
38
+ * of entry filenames to their actual output filenames (which may be hashed in production).
39
+ *
40
+ * This plugin is adapted from the webpack version to work with Rspack's API.
41
+ * It provides the same functionality for frameworks that need to know the relationship
42
+ * between entry points and their corresponding built assets.
43
+ *
44
+ * ## Features
45
+ * - Maps entry points to their output files
46
+ * - Tracks bundle files and their relationships
47
+ * - Collects polyfill files
48
+ * - Supports loadable modules/chunks
49
+ * - Handles both development and production builds
50
+ *
51
+ * ## Generated Manifest Structure
52
+ * ```json
53
+ * {
54
+ * "entries": {
55
+ * "main": {
56
+ * "js": ["/static/js/main.abc123.js"],
57
+ * "css": ["/static/css/main.def456.css"]
58
+ * }
59
+ * },
60
+ * "bundles": {
61
+ * "main": "/static/js/main.abc123.js"
62
+ * },
63
+ * "chunkRequest": {
64
+ * "/static/js/chunk.xyz789.js": "/pages/about"
65
+ * },
66
+ * "loadble": {
67
+ * "/pages/about": {
68
+ * "files": ["/static/js/chunk.xyz789.js"],
69
+ * "children": [
70
+ * {
71
+ * "id": "module-id",
72
+ * "name": "AboutPage"
73
+ * }
74
+ * ]
75
+ * }
76
+ * },
77
+ * "polyfillFiles": ["/static/js/polyfills.ghi012.js"]
78
+ * }
79
+ * ```
80
+ *
81
+ * ## Usage Example
82
+ * ```typescript
83
+ * // In rspack configuration
84
+ * const RspackBuildManifestPlugin = require('./rspack-build-manifest-plugin');
85
+ *
86
+ * module.exports = {
87
+ * plugins: [
88
+ * new RspackBuildManifestPlugin({
89
+ * filename: 'build-manifest.json',
90
+ * modules: true,
91
+ * chunkRequest: true
92
+ * })
93
+ * ]
94
+ * };
95
+ * ```
96
+ *
97
+ * ## Use Cases
98
+ * - **SSR Frameworks**: Map entry points to built assets for server-side rendering
99
+ * - **Asset Loading**: Determine which files to load for specific routes
100
+ * - **Cache Management**: Track hashed filenames for cache invalidation
101
+ * - **Code Splitting**: Understand relationships between chunks and their requests
102
+ *
103
+ * ## Differences from Webpack Version
104
+ * - Uses Rspack's API instead of Webpack's
105
+ * - Simplified module collection (Rspack has different module APIs)
106
+ * - Adapted to Rspack's chunk and compilation structure
107
+ * - Some features are marked with TODO comments for future Rspack API support
108
+ */
109
+ class RspackBuildManifestPlugin {
110
+ /**
111
+ * Creates a new RspackBuildManifestPlugin instance
112
+ * @param options - Configuration options for the plugin
113
+ *
114
+ * @example
115
+ * ```typescript
116
+ * // Basic usage with default options
117
+ * new RspackBuildManifestPlugin()
118
+ *
119
+ * // Custom configuration
120
+ * new RspackBuildManifestPlugin({
121
+ * filename: 'assets-manifest.json',
122
+ * modules: true,
123
+ * chunkRequest: true
124
+ * })
125
+ * ```
126
+ */
127
+ constructor(options = {}) {
128
+ this._options = Object.assign(Object.assign({}, defaultOptions), options);
129
+ }
130
+ /**
131
+ * Creates the build manifest by analyzing compilation assets and chunks
132
+ * @param compiler - Rspack compiler instance
133
+ * @param compilation - Rspack compilation instance
134
+ * @returns The generated manifest object
135
+ *
136
+ * This method performs the following operations:
137
+ * 1. Initializes the manifest structure
138
+ * 2. Collects entry point information
139
+ * 3. Processes chunk groups and their assets
140
+ * 4. Identifies polyfill files
141
+ * 5. Sorts and organizes loadable modules
142
+ */
143
+ createAssets(compiler, compilation) {
144
+ const assetMap = (this._manifest = {
145
+ entries: {},
146
+ bundles: {},
147
+ chunkRequest: {},
148
+ loadble: {}
149
+ });
150
+ // TODO: Rspack 需要实现 chunk root modules 映射
151
+ // Webpack 版本使用 chunkGraph.getChunkRootModules() 来创建映射
152
+ // Rspack 可能需要使用不同的 API 或等待相关功能支持
153
+ // const chunkRootModulesMap = new Map<ModuleId, Boolean>();
154
+ // compilation.chunks.forEach(chunk => {
155
+ // const { chunkGraph } = compilation;
156
+ // if (chunkGraph) {
157
+ // chunkGraph.getChunkRootModules(chunk).forEach(module => {
158
+ // const id = chunkGraph.getModuleId(module);
159
+ // if (id !== '') {
160
+ // chunkRootModulesMap.set(id, true);
161
+ // }
162
+ // });
163
+ // }
164
+ // });
165
+ // Process all chunk groups
166
+ compilation.chunkGroups.forEach(chunkGroup => {
167
+ // Check if this is an entry point
168
+ if (chunkGroup.isInitial()) {
169
+ this._collectEntries(chunkGroup);
170
+ }
171
+ this._collect(chunkGroup, compiler, compilation);
172
+ });
173
+ const compilationAssets = compilation.getAssets();
174
+ // Collect polyfill files
175
+ this._manifest.polyfillFiles = compilationAssets
176
+ .filter(p => {
177
+ // Ensure only .js files are passed through
178
+ if (!p.name.endsWith('.js')) {
179
+ return false;
180
+ }
181
+ return p.info && shared_1.BUILD_CLIENT_RUNTIME_POLYFILLS_SYMBOL in p.info;
182
+ })
183
+ .map(v => v.name);
184
+ // Sort loadable modules for consistent output
185
+ this._manifest.loadble = Object.keys(this._manifest.loadble)
186
+ .sort()
187
+ // eslint-disable-next-line no-sequences
188
+ .reduce((a, c) => ((a[c] = this._manifest.loadble[c]), a), {});
189
+ return assetMap;
190
+ }
191
+ /**
192
+ * Applies the plugin to the rspack compiler
193
+ * @param compiler - Rspack compiler instance
194
+ *
195
+ * This method hooks into the rspack compilation process to:
196
+ * 1. Listen for the 'make' hook to prepare for asset processing
197
+ * 2. Hook into 'processAssets' to generate the manifest file
198
+ * 3. Create the JSON file as a compilation asset
199
+ */
200
+ apply(compiler) {
201
+ compiler.hooks.make.tap('RspackBuildManifestPlugin', compilation => {
202
+ compilation.hooks.processAssets.tap({
203
+ name: 'RspackBuildManifestPlugin',
204
+ stage: webpack_1.rspack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONS
205
+ }, assets => {
206
+ assets[this._options.filename] = new RawSource(JSON.stringify(this.createAssets(compiler, compilation), null, 2), true);
207
+ });
208
+ });
209
+ }
210
+ /**
211
+ * Get the raw request of the chunk
212
+ * @param compilation - Rspack compilation instance
213
+ * @param chunk - Rspack chunk instance
214
+ * @returns The raw request of the chunk
215
+ */
216
+ _getFirstRawRequest(compilation, chunk) {
217
+ const modules = compilation.chunkGraph.getChunkModules(chunk);
218
+ const rawRequests = modules
219
+ .map(m => {
220
+ const rawRequest = m.rawRequest;
221
+ if (rawRequest) {
222
+ return rawRequest;
223
+ }
224
+ })
225
+ .filter(Boolean);
226
+ if (rawRequests.length > 1) {
227
+ console.debug(`[Rspack] chunk="${chunk.name}" rawRequests length > 1: length=${rawRequests.length}`);
228
+ /**
229
+ * @rspack-diff
230
+ * @TODO workaround: because Rspack's behavior is different from webpack
231
+ * if rawRequests length > 1,
232
+ * then try to return the sideEffectFree module's rawRequest
233
+ */
234
+ const sideEffectFreeModules = modules.filter(m => {
235
+ const factoryMeta = m.factoryMeta;
236
+ return factoryMeta && factoryMeta.sideEffectFree === true;
237
+ });
238
+ if (sideEffectFreeModules.length) {
239
+ const rawRequest = sideEffectFreeModules[0]
240
+ .rawRequest;
241
+ console.debug(`[Rspack] chunk="${chunk.name}" try to pick sideEffectFreeModules="${rawRequest}"`);
242
+ if (rawRequest) {
243
+ return rawRequest;
244
+ }
245
+ }
246
+ }
247
+ return rawRequests[0] || '';
248
+ }
249
+ /**
250
+ * Collects entry point information from chunk groups
251
+ * @param entrypoint - The entry point chunk group to process
252
+ *
253
+ * This method processes entry points and maps them to their output files,
254
+ * filtering out source maps and hot update files.
255
+ */
256
+ _collectEntries(entrypoint) {
257
+ for (const chunk of entrypoint.chunks) {
258
+ // If there's no name or no files
259
+ if (!chunk.name || !chunk.files) {
260
+ continue;
261
+ }
262
+ for (const file of chunk.files) {
263
+ if (/\.map$/.test(file) || /\.hot-update\.js$/.test(file)) {
264
+ continue;
265
+ }
266
+ const ext = getFileExt(file);
267
+ this._pushEntries(entrypoint.name, ext, file.replace(/\\/g, '/'));
268
+ }
269
+ }
270
+ }
271
+ /**
272
+ * Collects information from chunk groups including chunks and modules
273
+ * @param chunkGroup - The chunk group to process
274
+ * @param compiler - Rspack compiler instance
275
+ * @param compilation - Rspack compilation instance
276
+ *
277
+ * This method processes chunk groups to collect:
278
+ * - Chunk information (files, requests)
279
+ * - Module information (if enabled)
280
+ * - Loadable modules and their relationships
281
+ */
282
+ _collect(chunkGroup, compiler, compilation) {
283
+ const collectModules = this._options.modules;
284
+ // Rspack chunk groups may have origins with request information
285
+ const origins = chunkGroup.origins || [];
286
+ origins.forEach(chunkGroupOrigin => {
287
+ const { request } = chunkGroupOrigin;
288
+ const ctx = { request: request || '', compiler, compilation };
289
+ chunkGroup.chunks.forEach(chunk => {
290
+ const rawRequest = this._getFirstRawRequest(compilation, chunk);
291
+ if (rawRequest) {
292
+ // @TODO lodable support
293
+ // ctx.request = rawRequest;
294
+ }
295
+ this._collectChunk(chunk, ctx);
296
+ if (collectModules) {
297
+ this._collectChunkModule(chunk, ctx);
298
+ }
299
+ });
300
+ });
301
+ // If no origins, still process chunks
302
+ if (origins.length === 0) {
303
+ chunkGroup.chunks.forEach(chunk => {
304
+ this._collectChunk(chunk, { request: '', compilation, compiler });
305
+ if (collectModules) {
306
+ this._collectChunkModule(chunk, {
307
+ request: '',
308
+ compiler,
309
+ compilation
310
+ });
311
+ }
312
+ });
313
+ }
314
+ }
315
+ /**
316
+ * Collects information from individual chunks
317
+ * @param chunk - The rspack chunk to process
318
+ * @param request - The request that generated this chunk
319
+ *
320
+ * This method processes chunks to:
321
+ * - Identify bundle files
322
+ * - Map chunk requests to files
323
+ * - Filter out source maps and hot update files
324
+ */
325
+ _collectChunk(chunk, { request, compilation, compiler }) {
326
+ if (!chunk.files) {
327
+ return;
328
+ }
329
+ for (const file of chunk.files) {
330
+ if (/\.map$/.test(file) || /\.hot-update\.js$/.test(file)) {
331
+ continue;
332
+ }
333
+ const ext = getFileExt(file);
334
+ const normalizedPath = file.replace(/\\/g, '/');
335
+ // normal chunk
336
+ if (ext === 'js') {
337
+ if (chunk.isOnlyInitial()) {
338
+ this._pushBundle({
339
+ name: chunk.name,
340
+ file: normalizedPath
341
+ });
342
+ }
343
+ this._pushChunkRequest({
344
+ file: normalizedPath,
345
+ request: request
346
+ });
347
+ }
348
+ }
349
+ }
350
+ /**
351
+ * Collects module information from chunks (when modules option is enabled)
352
+ * @param chunk - The rspack chunk to process
353
+ * @param request - The request that generated this chunk
354
+ * @param compiler - Rspack compiler instance
355
+ * @param compilation - Rspack compilation instance
356
+ *
357
+ * This method processes chunks to collect:
358
+ * - Loadable module files (JS and CSS)
359
+ * - Module metadata (ID, name) - simplified for Rspack
360
+ * - Root modules for code splitting analysis
361
+ */
362
+ _collectChunkModule(chunk, { request, compiler, compilation }) {
363
+ if (chunk.canBeInitial()) {
364
+ return;
365
+ }
366
+ const context = compiler.options.context;
367
+ // Collect files from chunk
368
+ chunk.files.forEach((file) => {
369
+ const isJs = file.match(/\.js$/) && file.match(/^static\/chunks\//);
370
+ const isCss = file.match(/\.css$/) && file.match(/^static\/css\//);
371
+ if (isJs || isCss) {
372
+ this._pushLoadableModules(request, file);
373
+ }
374
+ });
375
+ // TODO: Rspack 模块收集需要适配
376
+ // Webpack 版本使用 chunkGraph.getChunkModulesIterable() 和 chunkRootModulesMap
377
+ // Rspack 可能需要使用不同的 API 或等待相关功能支持
378
+ // 当前实现是简化版本,可能需要根据 Rspack 的实际 API 进行调整
379
+ try {
380
+ const { chunkGraph } = compilation;
381
+ if (chunkGraph &&
382
+ typeof chunkGraph.getChunkModulesIterable === 'function') {
383
+ for (const module of chunkGraph.getChunkModulesIterable(chunk)) {
384
+ let id = chunkGraph.getModuleId(module);
385
+ if (!module.type || !module.type.startsWith('javascript')) {
386
+ continue;
387
+ }
388
+ let name = null;
389
+ if (typeof module.libIdent === 'function') {
390
+ name = module.libIdent({ context });
391
+ }
392
+ if (!name || name.endsWith('.css')) {
393
+ continue;
394
+ }
395
+ // TODO: Rspack 需要实现 chunk root modules 检查
396
+ // Webpack 版本使用 chunkRootModulesMap.has(id) 来检查是否为根模块
397
+ // Rspack 可能需要使用不同的方式来判断模块是否为根模块
398
+ // 当前暂时将所有模块都作为 loadable 模块处理
399
+ // const isRootModule = chunkRootModulesMap.has(id);
400
+ // if (isRootModule) {
401
+ this._pushLoadableModules(request, {
402
+ id,
403
+ name
404
+ });
405
+ // }
406
+ }
407
+ }
408
+ }
409
+ catch (error) {
410
+ // Rspack might not have the same module APIs as webpack
411
+ // This is a fallback for compatibility
412
+ console.warn('RspackBuildManifestPlugin: Module collection not fully supported in this Rspack version');
413
+ }
414
+ }
415
+ /**
416
+ * Adds entry information to the manifest
417
+ * @param name - Entry point name
418
+ * @param ext - File extension
419
+ * @param value - File path
420
+ *
421
+ * @example
422
+ * ```typescript
423
+ * this._pushEntries('main', 'js', '/static/js/main.abc123.js')
424
+ * // Results in: { entries: { main: { js: ['/static/js/main.abc123.js'] } } }
425
+ * ```
426
+ */
427
+ _pushEntries(name, ext, value) {
428
+ const entries = this._manifest.entries;
429
+ if (!entries[name]) {
430
+ entries[name] = {
431
+ js: []
432
+ };
433
+ }
434
+ if (!entries[name][ext]) {
435
+ entries[name][ext] = [value];
436
+ }
437
+ else {
438
+ entries[name][ext].push(value);
439
+ }
440
+ }
441
+ /**
442
+ * Adds bundle information to the manifest
443
+ * @param name - Bundle name
444
+ * @param file - Bundle file path
445
+ *
446
+ * @example
447
+ * ```typescript
448
+ * this._pushBundle({ name: 'main', file: '/static/js/main.abc123.js' })
449
+ * // Results in: { bundles: { main: '/static/js/main.abc123.js' } }
450
+ * ```
451
+ */
452
+ _pushBundle({ name, file }) {
453
+ if (name) {
454
+ this._manifest.bundles[name] = file;
455
+ }
456
+ }
457
+ /**
458
+ * Adds chunk request information to the manifest (when chunkRequest option is enabled)
459
+ * @param file - Chunk file path
460
+ * @param request - Request that generated the chunk
461
+ *
462
+ * @example
463
+ * ```typescript
464
+ * this._pushChunkRequest({
465
+ * file: '/static/js/chunk.xyz789.js',
466
+ * request: '/pages/about'
467
+ * })
468
+ * // Results in: { chunkRequest: { '/static/js/chunk.xyz789.js': '/pages/about' } }
469
+ * ```
470
+ */
471
+ _pushChunkRequest({ file, request }) {
472
+ if (this._options.chunkRequest && request) {
473
+ this._manifest.chunkRequest[file] = request;
474
+ }
475
+ }
476
+ _pushLoadableModules(request, value) {
477
+ const modules = this._manifest.loadble;
478
+ if (!modules[request]) {
479
+ modules[request] = {
480
+ files: [],
481
+ children: []
482
+ };
483
+ }
484
+ if (typeof value === 'string') {
485
+ const existed = modules[request].files.some(file => file === value);
486
+ if (!existed) {
487
+ modules[request].files.push(value);
488
+ }
489
+ }
490
+ else {
491
+ const existed = modules[request].children.some(item => item.id === value.id);
492
+ if (!existed) {
493
+ modules[request].children.push(value);
494
+ }
495
+ }
496
+ }
497
+ }
498
+ exports.default = RspackBuildManifestPlugin;
@@ -28,11 +28,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
28
28
  Object.defineProperty(exports, "__esModule", { value: true });
29
29
  const path = __importStar(require("path"));
30
30
  const service_1 = require("@shuvi/service");
31
- const ReactRefreshWebpackPlugin_1 = __importDefault(require("@next/react-refresh-utils/ReactRefreshWebpackPlugin"));
31
+ const plugin_react_refresh_1 = __importDefault(require("@rspack/plugin-react-refresh"));
32
32
  const paths_1 = require("../../../paths");
33
33
  const shared_1 = require("../../../../shared");
34
34
  const isDefined = (value) => Boolean(value);
35
- const configWebpack = (config, { name, webpack }, context) => {
35
+ const configWebpack = (config, { name, rspack }, context) => {
36
36
  const resolveUser = (m, sub) => {
37
37
  const { rootDir } = context.paths;
38
38
  let userPkg = {
@@ -93,30 +93,19 @@ const configWebpack = (config, { name, webpack }, context) => {
93
93
  // @ts-ignore
94
94
  [resolveUser('react-dom'), (0, paths_1.resolveLocal)('react-dom')].filter(isDefined));
95
95
  if (name === shared_1.BUNDLER_TARGET_CLIENT) {
96
- config.plugin('version-env-plugin').use(webpack.DefinePlugin, [
96
+ config.plugin('version-env-plugin').use(rspack.DefinePlugin, [
97
97
  {
98
98
  'process.env.__SHUVI__AFTER__REACT__18__': JSON.stringify(isReactVersionAfter18())
99
99
  }
100
100
  ]);
101
101
  if (context.mode === 'development') {
102
- config
103
- .plugin('react-refresh-plugin')
104
- .use(ReactRefreshWebpackPlugin_1.default, [webpack]);
102
+ config.plugin('react-refresh-plugin').use(plugin_react_refresh_1.default);
105
103
  }
106
104
  }
107
105
  return config;
108
106
  };
109
107
  exports.default = {
110
108
  core: (0, service_1.createPlugin)({
111
- configWebpack,
112
- addEntryCode(context) {
113
- if (context.mode === 'development') {
114
- const fastRefreshRuntime = require.resolve(`@next/react-refresh-utils/runtime`);
115
- return `import "${fastRefreshRuntime}"`;
116
- }
117
- else {
118
- return '';
119
- }
120
- }
109
+ configWebpack
121
110
  })
122
111
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shuvi/platform-web",
3
- "version": "1.0.62",
3
+ "version": "2.0.0-dev.6",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/shuvijs/shuvi.git",
@@ -71,16 +71,16 @@
71
71
  "node": ">= 16.0.0"
72
72
  },
73
73
  "dependencies": {
74
- "@next/react-refresh-utils": "12.1.6",
75
- "@shuvi/error-overlay": "1.0.62",
76
- "@shuvi/hook": "1.0.62",
77
- "@shuvi/platform-shared": "1.0.62",
78
- "@shuvi/router": "1.0.62",
79
- "@shuvi/router-react": "1.0.62",
80
- "@shuvi/runtime": "1.0.62",
81
- "@shuvi/shared": "1.0.62",
82
- "@shuvi/toolpack": "1.0.62",
83
- "@shuvi/utils": "1.0.62",
74
+ "@rspack/plugin-react-refresh": "^1.4.3",
75
+ "@shuvi/error-overlay": "2.0.0-dev.6",
76
+ "@shuvi/hook": "2.0.0-dev.6",
77
+ "@shuvi/platform-shared": "2.0.0-dev.6",
78
+ "@shuvi/router": "2.0.0-dev.6",
79
+ "@shuvi/router-react": "2.0.0-dev.6",
80
+ "@shuvi/runtime": "2.0.0-dev.6",
81
+ "@shuvi/shared": "2.0.0-dev.6",
82
+ "@shuvi/toolpack": "2.0.0-dev.6",
83
+ "@shuvi/utils": "2.0.0-dev.6",
84
84
  "content-type": "1.0.4",
85
85
  "core-js": "3.6.5",
86
86
  "doura": "0.0.13",
@@ -98,7 +98,7 @@
98
98
  "whatwg-fetch": "3.0.0"
99
99
  },
100
100
  "peerDependencies": {
101
- "@shuvi/service": "1.0.62"
101
+ "@shuvi/service": "2.0.0-dev.6"
102
102
  },
103
103
  "devDependencies": {
104
104
  "@shuvi/service": "workspace:*",