@meteorjs/rspack 0.0.16 → 0.0.18

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.16",
3
+ "version": "0.0.18",
4
4
  "description": "Configuration logic for using Rspack in Meteor projects",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -0,0 +1,53 @@
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
+ 'public/_build-bundles',
21
+ 'private/_build-assets',
22
+ ];
23
+
24
+ this.targets = Array.isArray(options.targets)
25
+ ? options.targets
26
+ : defaults;
27
+
28
+ this.verbose = options.verbose || false;
29
+ }
30
+
31
+ apply(compiler) {
32
+ compiler.hooks.beforeCompile.tapPromise(
33
+ 'CleanBuildAssetsPlugin',
34
+ async () => {
35
+ for (const target of this.targets) {
36
+ const dir = path.resolve(compiler.context, target);
37
+ try {
38
+ await fs.rm(dir, { recursive: true, force: true });
39
+ await fs.mkdir(dir, { recursive: true });
40
+ if (this.verbose) {
41
+ console.log(`[CleanBuildAssetsPlugin] Cleaned: ${dir}`);
42
+ }
43
+ } catch (err) {
44
+ console.warn(
45
+ `[CleanBuildAssetsPlugin] Failed to clean ${dir}:`,
46
+ err.message
47
+ );
48
+ }
49
+ }
50
+ }
51
+ )
52
+ }
53
+ }
@@ -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
@@ -156,7 +159,7 @@ export default function (inMeteor = {}, argv = {}) {
156
159
  // Set watch options
157
160
  const watchOptions = {
158
161
  ...defaultWatchOptions,
159
- ...isTestEager && {
162
+ ...isTest && isTestEager && {
160
163
  ignored: [
161
164
  ...defaultWatchOptions.ignored,
162
165
  '**/_build/**',
@@ -232,8 +235,14 @@ 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`,
237
246
  },
238
247
  optimization: {
239
248
  usedExports: true,
@@ -256,6 +265,9 @@ export default function (inMeteor = {}, argv = {}) {
256
265
  resolve: { extensions, alias },
257
266
  externals,
258
267
  plugins: [
268
+ ...(isProd ? [
269
+ new CleanBuildAssetsPlugin({ verbose: Meteor.isDebug || Meteor.isVerbose }),
270
+ ] : []),
259
271
  ...[
260
272
  ...(isReactEnabled && reactRefreshModule && isDevEnvironment
261
273
  ? [new reactRefreshModule()]
@@ -273,6 +285,24 @@ export default function (inMeteor = {}, argv = {}) {
273
285
  banner: bannerOutput,
274
286
  entryOnly: true,
275
287
  }),
288
+ new HtmlRspackPlugin({
289
+ inject: false,
290
+ cache: true,
291
+ filename: `../${buildContext}/${outputDir}/index.html`,
292
+ templateContent: `
293
+ <head>
294
+ <% for tag in htmlRspackPlugin.tags.headTags { %>
295
+ <%= toHtml(tag) %>
296
+ <% } %>
297
+ </head>
298
+ <body>
299
+ <% for tag in htmlRspackPlugin.tags.bodyTags { %>
300
+ <%= toHtml(tag) %>
301
+ <% } %>
302
+ </body>
303
+ `,
304
+ }),
305
+ new RspackMeteorHtmlPlugin({}),
276
306
  ],
277
307
  watchOptions,
278
308
  devtool: isDevEnvironment || isTest ? 'source-map' : 'hidden-source-map',
@@ -284,10 +314,12 @@ export default function (inMeteor = {}, argv = {}) {
284
314
  ...(Meteor.isBlazeEnabled && { hot: false }),
285
315
  port: Meteor.devServerPort || 8080,
286
316
  devMiddleware: {
287
- writeToDisk: false,
317
+ writeToDisk: (filePath) =>
318
+ /\.(html)$/.test(filePath) && !filePath.includes('.hot-update.'),
288
319
  },
289
320
  },
290
321
  }),
322
+ experiments: { css: true },
291
323
  };
292
324
 
293
325
  const serverNameConfig = `[${isTest && 'test-' || ''}${isTestModule && 'module' || 'server'}-rspack]`;
@@ -296,7 +328,7 @@ export default function (inMeteor = {}, argv = {}) {
296
328
  name: serverNameConfig,
297
329
  target: 'node',
298
330
  mode,
299
- entry: isTestEager
331
+ entry: isTest && isTestEager
300
332
  ? "node_modules/@meteorjs/rspack/entries/eager-tests.js"
301
333
  : path.resolve(process.cwd(), buildContext, entryPath),
302
334
  output: {
@@ -325,7 +357,7 @@ export default function (inMeteor = {}, argv = {}) {
325
357
  externals,
326
358
  plugins: [
327
359
  new DefinePlugin(
328
- isTestModule || isTestEager
360
+ isTest && (isTestModule || isTestEager)
329
361
  ? {
330
362
  "Meteor.isTest": JSON.stringify(isTest),
331
363
  "Meteor.isDevelopment": JSON.stringify(isDev),