@meteorjs/rspack 0.0.17 → 0.0.19

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meteorjs/rspack",
3
- "version": "0.0.17",
3
+ "version": "0.0.19",
4
4
  "description": "Configuration logic for using Rspack in Meteor projects",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -0,0 +1,52 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+
4
+ /**
5
+ * Rspack plugin to clean and recreate build directories
6
+ * before each compilation.
7
+ *
8
+ * Options:
9
+ * - targets {string[]} : Directories to clean.
10
+ * Defaults:
11
+ * - public/_build-assets
12
+ * - public/_build-bundles
13
+ * - private/_build-assets
14
+ * - verbose {boolean} : If true, logs cleaning operations. Default: false
15
+ */
16
+ export default class CleanBuildAssetsPlugin {
17
+ constructor(options = {}) {
18
+ const defaults = [
19
+ 'public/_build-assets',
20
+ 'private/_build-assets',
21
+ ];
22
+
23
+ this.targets = Array.isArray(options.targets)
24
+ ? options.targets
25
+ : defaults;
26
+
27
+ this.verbose = options.verbose || false;
28
+ }
29
+
30
+ apply(compiler) {
31
+ compiler.hooks.beforeCompile.tapPromise(
32
+ 'CleanBuildAssetsPlugin',
33
+ async () => {
34
+ for (const target of this.targets) {
35
+ const dir = path.resolve(compiler.context, target);
36
+ try {
37
+ await fs.rm(dir, { recursive: true, force: true });
38
+ await fs.mkdir(dir, { recursive: true });
39
+ if (this.verbose) {
40
+ console.log(`[CleanBuildAssetsPlugin] Cleaned: ${dir}`);
41
+ }
42
+ } catch (err) {
43
+ console.warn(
44
+ `[CleanBuildAssetsPlugin] Failed to clean ${dir}:`,
45
+ err.message
46
+ );
47
+ }
48
+ }
49
+ }
50
+ )
51
+ }
52
+ }
@@ -0,0 +1,47 @@
1
+ import path from 'node:path';
2
+ import { createRequire } from 'node:module';
3
+
4
+ function loadHtmlRspackPluginFromHost(compiler) {
5
+ // Prefer the compiler's context; fall back to process.cwd()
6
+ const ctx = compiler.options?.context || compiler.context || process.cwd();
7
+ const requireFromHost = createRequire(path.join(ctx, 'package.json'));
8
+
9
+ const core = requireFromHost('@rspack/core'); // host's instance
10
+ // Rspack exports can be shaped a couple ways; be defensive
11
+ return core.HtmlRspackPlugin || core.rspack?.HtmlRspackPlugin || core.default?.HtmlRspackPlugin;
12
+ }
13
+
14
+ /**
15
+ * Rspack plugin to:
16
+ * 1. Remove the injected `client-rspack.js` script tag
17
+ * 2. Strip <!doctype> and <html>…</html> wrappers from the final HTML
18
+ */
19
+ export default class RspackMeteorHtmlPlugin {
20
+ apply(compiler) {
21
+ const HtmlRspackPlugin = loadHtmlRspackPluginFromHost(compiler);
22
+ if (!HtmlRspackPlugin?.getCompilationHooks) {
23
+ throw new Error('Could not load HtmlRspackPlugin from host project.');
24
+ }
25
+
26
+ compiler.hooks.compilation.tap('RspackMeteorHtmlPlugin', compilation => {
27
+ const hooks = HtmlRspackPlugin.getCompilationHooks(compilation);
28
+
29
+ // remove <script src=".../client-rspack.js">
30
+ hooks.alterAssetTags.tap('RspackMeteorHtmlPlugin', data => {
31
+ data.assetTags.scripts = data.assetTags.scripts.filter(t => {
32
+ const src = t.attributes?.src || t.asset || '';
33
+ return !(t.tagName === 'script' && /(?:^|\/)client-rspack\.js$/i.test(src));
34
+ });
35
+ });
36
+
37
+ // unwrap <!doctype> and <html>…</html>
38
+ hooks.beforeEmit.tap('RspackMeteorHtmlPlugin', data => {
39
+ data.html = data.html
40
+ .replace(/<!doctype[^>]*>\s*/i, '')
41
+ .replace(/<html[^>]*>\s*/i, '')
42
+ .replace(/\s*<\/html>\s*$/i, '')
43
+ .trim();
44
+ });
45
+ });
46
+ }
47
+ }
package/rspack.config.js CHANGED
@@ -1,4 +1,4 @@
1
- import { DefinePlugin, BannerPlugin } from '@rspack/core';
1
+ import { DefinePlugin, BannerPlugin, HtmlRspackPlugin } from '@rspack/core';
2
2
  import fs from 'fs';
3
3
  import { createRequire } from 'module';
4
4
  import path from 'path';
@@ -8,6 +8,8 @@ import { inspect } from "node:util";
8
8
  import { RequireExternalsPlugin } from './plugins/RequireExtenalsPlugin.js';
9
9
  import { getMeteorAppSwcConfig } from "./lib/swc.js";
10
10
  import { mergeSplitOverlap } from './lib/mergeRulesSplitOverlap.js';
11
+ import CleanBuildAssetsPlugin from "./plugins/CleanBuildAssetsPlugin.js";
12
+ import RspackMeteorHtmlPlugin from "./plugins/RspackMeteorHtmlPlugin.js";
11
13
 
12
14
  const require = createRequire(import.meta.url);
13
15
 
@@ -136,6 +138,7 @@ export default function (inMeteor = {}, argv = {}) {
136
138
 
137
139
  // Determine output points
138
140
  const outputPath = Meteor.outputPath;
141
+ const outputDir = path.dirname(Meteor.outputPath || '');
139
142
  const outputFilename = Meteor.outputFilename;
140
143
 
141
144
  // Determine run point
@@ -232,8 +235,15 @@ export default function (inMeteor = {}, argv = {}) {
232
235
  isDevEnvironment ? outputFilename : `../${buildContext}/${outputPath}`,
233
236
  libraryTarget: 'commonjs',
234
237
  publicPath: '/',
235
- chunkFilename: `${bundlesContext}/[id].[chunkhash].js`,
238
+ chunkFilename: `${bundlesContext}/[id]${isProd ? '.[chunkhash]' : ''}.js`,
236
239
  assetModuleFilename: `${assetsContext}/[hash][ext][query]`,
240
+ cssFilename: `${assetsContext}/[name]${
241
+ isProd ? '.[contenthash]' : ''
242
+ }.css`,
243
+ cssChunkFilename: `${assetsContext}/[id]${
244
+ isProd ? '.[contenthash]' : ''
245
+ }.css`,
246
+ clean: isProd,
237
247
  },
238
248
  optimization: {
239
249
  usedExports: true,
@@ -256,6 +266,9 @@ export default function (inMeteor = {}, argv = {}) {
256
266
  resolve: { extensions, alias },
257
267
  externals,
258
268
  plugins: [
269
+ ...(isProd ? [
270
+ new CleanBuildAssetsPlugin({ verbose: Meteor.isDebug || Meteor.isVerbose }),
271
+ ] : []),
259
272
  ...[
260
273
  ...(isReactEnabled && reactRefreshModule && isDevEnvironment
261
274
  ? [new reactRefreshModule()]
@@ -273,6 +286,24 @@ export default function (inMeteor = {}, argv = {}) {
273
286
  banner: bannerOutput,
274
287
  entryOnly: true,
275
288
  }),
289
+ new HtmlRspackPlugin({
290
+ inject: false,
291
+ cache: true,
292
+ filename: `../${buildContext}/${outputDir}/index.html`,
293
+ templateContent: `
294
+ <head>
295
+ <% for tag in htmlRspackPlugin.tags.headTags { %>
296
+ <%= toHtml(tag) %>
297
+ <% } %>
298
+ </head>
299
+ <body>
300
+ <% for tag in htmlRspackPlugin.tags.bodyTags { %>
301
+ <%= toHtml(tag) %>
302
+ <% } %>
303
+ </body>
304
+ `,
305
+ }),
306
+ new RspackMeteorHtmlPlugin(),
276
307
  ],
277
308
  watchOptions,
278
309
  devtool: isDevEnvironment || isTest ? 'source-map' : 'hidden-source-map',
@@ -284,10 +315,12 @@ export default function (inMeteor = {}, argv = {}) {
284
315
  ...(Meteor.isBlazeEnabled && { hot: false }),
285
316
  port: Meteor.devServerPort || 8080,
286
317
  devMiddleware: {
287
- writeToDisk: false,
318
+ writeToDisk: (filePath) =>
319
+ /\.(html)$/.test(filePath) && !filePath.includes('.hot-update.'),
288
320
  },
289
321
  },
290
322
  }),
323
+ experiments: { css: true },
291
324
  };
292
325
 
293
326
  const serverNameConfig = `[${isTest && 'test-' || ''}${isTestModule && 'module' || 'server'}-rspack]`;