@nx/rspack 21.0.0-beta.8 → 21.0.0-beta.9
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/migrations.json +0 -34
- package/package.json +6 -5
- package/src/generators/convert-webpack/lib/transform-cjs.js +81 -0
- package/src/generators/convert-webpack/lib/transform-esm.js +81 -0
- package/src/plugins/utils/apply-web-config.js +22 -8
- package/src/plugins/utils/models.d.ts +5 -0
- package/src/plugins/utils/plugins/normalize-options.js +1 -0
- package/src/plugins/write-index-html-plugin.d.ts +22 -0
- package/src/plugins/write-index-html-plugin.js +250 -0
- package/src/utils/webpack/interpolate-env-variables-to-index.d.ts +1 -0
- package/src/utils/webpack/interpolate-env-variables-to-index.js +29 -0
- package/src/utils/webpack/normalize-entry.d.ts +2 -0
- package/src/utils/webpack/normalize-entry.js +27 -0
- package/src/utils/webpack/package-chunk-sort.d.ts +5 -0
- package/src/utils/webpack/package-chunk-sort.js +30 -0
- package/src/utils/with-nx.js +1 -0
- package/src/utils/with-web.d.ts +4 -0
- package/src/utils/with-web.js +1 -0
package/migrations.json
CHANGED
|
@@ -14,40 +14,6 @@
|
|
|
14
14
|
}
|
|
15
15
|
},
|
|
16
16
|
"packageJsonUpdates": {
|
|
17
|
-
"18.1.0": {
|
|
18
|
-
"version": "18.1.0-beta.0",
|
|
19
|
-
"packages": {
|
|
20
|
-
"@rspack/core": {
|
|
21
|
-
"version": "~0.5.6",
|
|
22
|
-
"alwaysAddToPackageJson": false
|
|
23
|
-
},
|
|
24
|
-
"@rspack/dev-server": {
|
|
25
|
-
"version": "~0.5.6",
|
|
26
|
-
"alwaysAddToPackageJson": false
|
|
27
|
-
},
|
|
28
|
-
"@rspack/plugin-minify": {
|
|
29
|
-
"version": "~0.5.6",
|
|
30
|
-
"alwaysAddToPackageJson": false
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
},
|
|
34
|
-
"18.1.3": {
|
|
35
|
-
"version": "18.1.3",
|
|
36
|
-
"packages": {
|
|
37
|
-
"@rspack/core": {
|
|
38
|
-
"version": "^0.6.1",
|
|
39
|
-
"alwaysAddToPackageJson": false
|
|
40
|
-
},
|
|
41
|
-
"@rspack/dev-server": {
|
|
42
|
-
"version": "^0.6.1",
|
|
43
|
-
"alwaysAddToPackageJson": false
|
|
44
|
-
},
|
|
45
|
-
"@rspack/plugin-minify": {
|
|
46
|
-
"version": "^0.6.1",
|
|
47
|
-
"alwaysAddToPackageJson": false
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
},
|
|
51
17
|
"19.3.0": {
|
|
52
18
|
"version": "19.3.0-beta.0",
|
|
53
19
|
"packages": {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nx/rspack",
|
|
3
3
|
"description": "The Nx Plugin for Rspack contains executors and generators that support building applications using Rspack.",
|
|
4
|
-
"version": "21.0.0-beta.
|
|
4
|
+
"version": "21.0.0-beta.9",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -24,10 +24,10 @@
|
|
|
24
24
|
"generators": "./generators.json",
|
|
25
25
|
"executors": "./executors.json",
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@nx/js": "21.0.0-beta.
|
|
28
|
-
"@nx/devkit": "21.0.0-beta.
|
|
29
|
-
"@nx/web": "21.0.0-beta.
|
|
30
|
-
"@nx/module-federation": "21.0.0-beta.
|
|
27
|
+
"@nx/js": "21.0.0-beta.9",
|
|
28
|
+
"@nx/devkit": "21.0.0-beta.9",
|
|
29
|
+
"@nx/web": "21.0.0-beta.9",
|
|
30
|
+
"@nx/module-federation": "21.0.0-beta.9",
|
|
31
31
|
"@phenomnomnominal/tsquery": "~5.0.1",
|
|
32
32
|
"@rspack/core": "^1.1.5",
|
|
33
33
|
"@rspack/dev-server": "^1.0.9",
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
"less-loader": "11.1.0",
|
|
43
43
|
"license-webpack-plugin": "^4.0.2",
|
|
44
44
|
"loader-utils": "^2.0.3",
|
|
45
|
+
"parse5": "4.0.0",
|
|
45
46
|
"sass": "^1.85.0",
|
|
46
47
|
"sass-embedded": "^1.83.4",
|
|
47
48
|
"sass-loader": "^16.0.4",
|
|
@@ -12,6 +12,87 @@ function transformCjsConfigFile(tree, configPath) {
|
|
|
12
12
|
transformWithModuleFederation(tree, configPath, scope);
|
|
13
13
|
transformWithModuleFederationSSR(tree, configPath, scope);
|
|
14
14
|
});
|
|
15
|
+
// Add useLegacyHtmlPlugin: true to withWeb() calls
|
|
16
|
+
transformWithWebCalls(tree, configPath);
|
|
17
|
+
}
|
|
18
|
+
function transformWithWebCalls(tree, configPath) {
|
|
19
|
+
const configContents = tree.read(configPath, 'utf-8');
|
|
20
|
+
const ast = tsquery_1.tsquery.ast(configContents);
|
|
21
|
+
// Find withWeb() calls
|
|
22
|
+
const withWebCallNodes = (0, tsquery_1.tsquery)(ast, 'CallExpression > Identifier[name=withWeb]');
|
|
23
|
+
// If there are withWeb calls, update them
|
|
24
|
+
if (withWebCallNodes.length > 0) {
|
|
25
|
+
let newContents = configContents;
|
|
26
|
+
for (const node of withWebCallNodes) {
|
|
27
|
+
const callExpr = node.parent;
|
|
28
|
+
if (!callExpr)
|
|
29
|
+
continue;
|
|
30
|
+
const startPos = callExpr.getStart();
|
|
31
|
+
const endPos = callExpr.getEnd();
|
|
32
|
+
const callText = configContents.substring(startPos, endPos);
|
|
33
|
+
// Skip if useLegacyHtmlPlugin is already present
|
|
34
|
+
if (callText.includes('useLegacyHtmlPlugin')) {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
// If it's already withWeb({ ... }), add useLegacyHtmlPlugin: true to the options
|
|
38
|
+
if (callText.includes('{') && callText.includes('}')) {
|
|
39
|
+
const newCallText = callText.replace(/\{\s*/, '{ useLegacyHtmlPlugin: true,\n ');
|
|
40
|
+
newContents =
|
|
41
|
+
newContents.substring(0, startPos) +
|
|
42
|
+
newCallText +
|
|
43
|
+
newContents.substring(endPos);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
// If it's just withWeb(), replace with withWeb({ useLegacyHtmlPlugin: true })
|
|
47
|
+
const newCallText = 'withWeb({ useLegacyHtmlPlugin: true })';
|
|
48
|
+
newContents =
|
|
49
|
+
newContents.substring(0, startPos) +
|
|
50
|
+
newCallText +
|
|
51
|
+
newContents.substring(endPos);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (newContents !== configContents) {
|
|
55
|
+
tree.write(configPath, newContents);
|
|
56
|
+
}
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
// If no withWeb calls, check for withReact calls
|
|
60
|
+
const withReactCallNodes = (0, tsquery_1.tsquery)(ast, 'CallExpression > Identifier[name=withReact]');
|
|
61
|
+
if (withReactCallNodes.length === 0) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
let newContents = configContents;
|
|
65
|
+
for (const node of withReactCallNodes) {
|
|
66
|
+
const callExpr = node.parent;
|
|
67
|
+
if (!callExpr)
|
|
68
|
+
continue;
|
|
69
|
+
const startPos = callExpr.getStart();
|
|
70
|
+
const endPos = callExpr.getEnd();
|
|
71
|
+
const callText = configContents.substring(startPos, endPos);
|
|
72
|
+
// Skip if useLegacyHtmlPlugin is already present
|
|
73
|
+
if (callText.includes('useLegacyHtmlPlugin')) {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
// If it's already withReact({ ... }), add useLegacyHtmlPlugin: true to the options
|
|
77
|
+
if (callText.includes('{') && callText.includes('}')) {
|
|
78
|
+
const newCallText = callText.replace(/\{\s*/, '{ useLegacyHtmlPlugin: true,\n ');
|
|
79
|
+
newContents =
|
|
80
|
+
newContents.substring(0, startPos) +
|
|
81
|
+
newCallText +
|
|
82
|
+
newContents.substring(endPos);
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
// If it's just withReact(), replace with withReact({ useLegacyHtmlPlugin: true })
|
|
86
|
+
const newCallText = 'withReact({ useLegacyHtmlPlugin: true })';
|
|
87
|
+
newContents =
|
|
88
|
+
newContents.substring(0, startPos) +
|
|
89
|
+
newCallText +
|
|
90
|
+
newContents.substring(endPos);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (newContents !== configContents) {
|
|
94
|
+
tree.write(configPath, newContents);
|
|
95
|
+
}
|
|
15
96
|
}
|
|
16
97
|
function transformComposePlugins(tree, configPath, scope) {
|
|
17
98
|
const configContents = tree.read(configPath, 'utf-8');
|
|
@@ -12,6 +12,87 @@ function transformEsmConfigFile(tree, configPath) {
|
|
|
12
12
|
transformWithModuleFederation(tree, configPath, scope);
|
|
13
13
|
transformWithModuleFederationSSR(tree, configPath, scope);
|
|
14
14
|
});
|
|
15
|
+
// Add useLegacyHtmlPlugin: true to withWeb() calls
|
|
16
|
+
transformWithWebCalls(tree, configPath);
|
|
17
|
+
}
|
|
18
|
+
function transformWithWebCalls(tree, configPath) {
|
|
19
|
+
const configContents = tree.read(configPath, 'utf-8');
|
|
20
|
+
const ast = tsquery_1.tsquery.ast(configContents);
|
|
21
|
+
// Find withWeb() calls
|
|
22
|
+
const withWebCallNodes = (0, tsquery_1.tsquery)(ast, 'CallExpression > Identifier[name=withWeb]');
|
|
23
|
+
// If there are withWeb calls, update them
|
|
24
|
+
if (withWebCallNodes.length > 0) {
|
|
25
|
+
let newContents = configContents;
|
|
26
|
+
for (const node of withWebCallNodes) {
|
|
27
|
+
const callExpr = node.parent;
|
|
28
|
+
if (!callExpr)
|
|
29
|
+
continue;
|
|
30
|
+
const startPos = callExpr.getStart();
|
|
31
|
+
const endPos = callExpr.getEnd();
|
|
32
|
+
const callText = configContents.substring(startPos, endPos);
|
|
33
|
+
// Skip if useLegacyHtmlPlugin is already present
|
|
34
|
+
if (callText.includes('useLegacyHtmlPlugin')) {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
// If it's already withWeb({ ... }), add useLegacyHtmlPlugin: true to the options
|
|
38
|
+
if (callText.includes('{') && callText.includes('}')) {
|
|
39
|
+
const newCallText = callText.replace(/\{\s*/, '{ useLegacyHtmlPlugin: true,\n ');
|
|
40
|
+
newContents =
|
|
41
|
+
newContents.substring(0, startPos) +
|
|
42
|
+
newCallText +
|
|
43
|
+
newContents.substring(endPos);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
// If it's just withWeb(), replace with withWeb({ useLegacyHtmlPlugin: true })
|
|
47
|
+
const newCallText = 'withWeb({ useLegacyHtmlPlugin: true })';
|
|
48
|
+
newContents =
|
|
49
|
+
newContents.substring(0, startPos) +
|
|
50
|
+
newCallText +
|
|
51
|
+
newContents.substring(endPos);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (newContents !== configContents) {
|
|
55
|
+
tree.write(configPath, newContents);
|
|
56
|
+
}
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
// If no withWeb calls, check for withReact calls
|
|
60
|
+
const withReactCallNodes = (0, tsquery_1.tsquery)(ast, 'CallExpression > Identifier[name=withReact]');
|
|
61
|
+
if (withReactCallNodes.length === 0) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
let newContents = configContents;
|
|
65
|
+
for (const node of withReactCallNodes) {
|
|
66
|
+
const callExpr = node.parent;
|
|
67
|
+
if (!callExpr)
|
|
68
|
+
continue;
|
|
69
|
+
const startPos = callExpr.getStart();
|
|
70
|
+
const endPos = callExpr.getEnd();
|
|
71
|
+
const callText = configContents.substring(startPos, endPos);
|
|
72
|
+
// Skip if useLegacyHtmlPlugin is already present
|
|
73
|
+
if (callText.includes('useLegacyHtmlPlugin')) {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
// If it's already withReact({ ... }), add useLegacyHtmlPlugin: true to the options
|
|
77
|
+
if (callText.includes('{') && callText.includes('}')) {
|
|
78
|
+
const newCallText = callText.replace(/\{\s*/, '{ useLegacyHtmlPlugin: true,\n ');
|
|
79
|
+
newContents =
|
|
80
|
+
newContents.substring(0, startPos) +
|
|
81
|
+
newCallText +
|
|
82
|
+
newContents.substring(endPos);
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
// If it's just withReact(), replace with withReact({ useLegacyHtmlPlugin: true })
|
|
86
|
+
const newCallText = 'withReact({ useLegacyHtmlPlugin: true })';
|
|
87
|
+
newContents =
|
|
88
|
+
newContents.substring(0, startPos) +
|
|
89
|
+
newCallText +
|
|
90
|
+
newContents.substring(endPos);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (newContents !== configContents) {
|
|
94
|
+
tree.write(configPath, newContents);
|
|
95
|
+
}
|
|
15
96
|
}
|
|
16
97
|
function transformComposePlugins(tree, configPath, scope) {
|
|
17
98
|
const configContents = tree.read(configPath, 'utf-8');
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.applyWebConfig = applyWebConfig;
|
|
4
4
|
const core_1 = require("@rspack/core");
|
|
5
|
+
const write_index_html_plugin_1 = require("../write-index-html-plugin");
|
|
5
6
|
const instantiate_script_plugins_1 = require("./instantiate-script-plugins");
|
|
6
7
|
const path_1 = require("path");
|
|
7
8
|
const hash_format_1 = require("./hash-format");
|
|
@@ -32,14 +33,27 @@ function applyWebConfig(options, config = {}, { useNormalizedEntry, } = {}) {
|
|
|
32
33
|
plugins.push(...(0, instantiate_script_plugins_1.instantiateScriptPlugins)(options));
|
|
33
34
|
}
|
|
34
35
|
if (options.index && options.generateIndexHtml) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
:
|
|
42
|
-
|
|
36
|
+
if (options.useLegacyHtmlPlugin) {
|
|
37
|
+
plugins.push(new write_index_html_plugin_1.WriteIndexHtmlPlugin({
|
|
38
|
+
indexPath: options.index,
|
|
39
|
+
outputPath: 'index.html',
|
|
40
|
+
baseHref: typeof options.baseHref === 'string' ? options.baseHref : undefined,
|
|
41
|
+
sri: options.subresourceIntegrity,
|
|
42
|
+
scripts: options.scripts,
|
|
43
|
+
styles: options.styles,
|
|
44
|
+
crossOrigin: config.output?.scriptType === 'module' ? 'anonymous' : undefined,
|
|
45
|
+
}));
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
plugins.push(new core_1.HtmlRspackPlugin({
|
|
49
|
+
template: options.index,
|
|
50
|
+
sri: options.subresourceIntegrity ? 'sha256' : undefined,
|
|
51
|
+
...(options.baseHref ? { base: { href: options.baseHref } } : {}),
|
|
52
|
+
...(config.output?.scriptType === 'module'
|
|
53
|
+
? { scriptLoading: 'module' }
|
|
54
|
+
: {}),
|
|
55
|
+
}));
|
|
56
|
+
}
|
|
43
57
|
}
|
|
44
58
|
const minimizer = [];
|
|
45
59
|
if (isProd && stylesOptimization) {
|
|
@@ -234,6 +234,10 @@ export interface NxAppRspackPluginOptions {
|
|
|
234
234
|
* Whether to rebase absolute path for assets in postcss cli resources.
|
|
235
235
|
*/
|
|
236
236
|
rebaseRootRelative?: boolean;
|
|
237
|
+
/**
|
|
238
|
+
* Use the legacy WriteIndexHtmlPlugin instead of the built-in HtmlRspackPlugin.
|
|
239
|
+
*/
|
|
240
|
+
useLegacyHtmlPlugin?: boolean;
|
|
237
241
|
}
|
|
238
242
|
export interface NormalizedNxAppRspackPluginOptions extends NxAppRspackPluginOptions {
|
|
239
243
|
projectName: string;
|
|
@@ -245,4 +249,5 @@ export interface NormalizedNxAppRspackPluginOptions extends NxAppRspackPluginOpt
|
|
|
245
249
|
projectGraph: ProjectGraph;
|
|
246
250
|
outputFileName: string;
|
|
247
251
|
assets: AssetGlobPattern[];
|
|
252
|
+
useLegacyHtmlPlugin: boolean;
|
|
248
253
|
}
|
|
@@ -65,6 +65,7 @@ function normalizeOptions(options) {
|
|
|
65
65
|
extractCss: combinedPluginAndMaybeExecutorOptions.extractCss ?? true,
|
|
66
66
|
fileReplacements: normalizeFileReplacements(devkit_1.workspaceRoot, combinedPluginAndMaybeExecutorOptions.fileReplacements),
|
|
67
67
|
generateIndexHtml: combinedPluginAndMaybeExecutorOptions.generateIndexHtml ?? true,
|
|
68
|
+
useLegacyHtmlPlugin: combinedPluginAndMaybeExecutorOptions.useLegacyHtmlPlugin ?? false,
|
|
68
69
|
main: combinedPluginAndMaybeExecutorOptions.main,
|
|
69
70
|
namedChunks: combinedPluginAndMaybeExecutorOptions.namedChunks ?? !isProd,
|
|
70
71
|
optimization: combinedPluginAndMaybeExecutorOptions.optimization ?? isProd,
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Compiler } from '@rspack/core';
|
|
2
|
+
import { ExtraEntryPoint } from '../utils/model';
|
|
3
|
+
export interface WriteIndexHtmlOptions {
|
|
4
|
+
indexPath: string;
|
|
5
|
+
outputPath: string;
|
|
6
|
+
baseHref?: string;
|
|
7
|
+
deployUrl?: string;
|
|
8
|
+
sri?: boolean;
|
|
9
|
+
scripts?: ExtraEntryPoint[];
|
|
10
|
+
styles?: ExtraEntryPoint[];
|
|
11
|
+
crossOrigin?: 'none' | 'anonymous' | 'use-credentials';
|
|
12
|
+
}
|
|
13
|
+
export declare class WriteIndexHtmlPlugin {
|
|
14
|
+
private readonly options;
|
|
15
|
+
constructor(options: WriteIndexHtmlOptions);
|
|
16
|
+
apply(compiler: Compiler): void;
|
|
17
|
+
private getEmittedFiles;
|
|
18
|
+
private stripBom;
|
|
19
|
+
private augmentIndexHtml;
|
|
20
|
+
private generateSriAttributes;
|
|
21
|
+
private filterAndMapBuildFiles;
|
|
22
|
+
}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.WriteIndexHtmlPlugin = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const rspack = tslib_1.__importStar(require("@rspack/core"));
|
|
6
|
+
const crypto_1 = require("crypto");
|
|
7
|
+
const fs_1 = require("fs");
|
|
8
|
+
const interpolate_env_variables_to_index_1 = require("../utils/webpack/interpolate-env-variables-to-index");
|
|
9
|
+
const package_chunk_sort_1 = require("../utils/webpack/package-chunk-sort");
|
|
10
|
+
const path_1 = require("path");
|
|
11
|
+
const parse5 = require('parse5');
|
|
12
|
+
class WriteIndexHtmlPlugin {
|
|
13
|
+
constructor(options) {
|
|
14
|
+
this.options = options;
|
|
15
|
+
}
|
|
16
|
+
apply(compiler) {
|
|
17
|
+
const { outputPath, indexPath, baseHref, deployUrl, sri = false, scripts = [], styles = [], crossOrigin, } = this.options;
|
|
18
|
+
compiler.hooks.thisCompilation.tap('WriteIndexHtmlPlugin', (compilation) => {
|
|
19
|
+
compilation.hooks.processAssets.tap({
|
|
20
|
+
name: 'WriteIndexHtmlPlugin',
|
|
21
|
+
// After minification and sourcemaps are done
|
|
22
|
+
stage: rspack.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_INLINE,
|
|
23
|
+
}, () => {
|
|
24
|
+
const moduleFiles = this.getEmittedFiles(compilation);
|
|
25
|
+
const files = moduleFiles.filter((x) => x.extension === '.css');
|
|
26
|
+
let content = (0, fs_1.readFileSync)(indexPath).toString();
|
|
27
|
+
content = this.stripBom(content);
|
|
28
|
+
compilation.assets[outputPath] = this.augmentIndexHtml({
|
|
29
|
+
input: outputPath,
|
|
30
|
+
inputContent: (0, interpolate_env_variables_to_index_1.interpolateEnvironmentVariablesToIndex)(content, deployUrl),
|
|
31
|
+
baseHref,
|
|
32
|
+
deployUrl,
|
|
33
|
+
crossOrigin,
|
|
34
|
+
sri,
|
|
35
|
+
entrypoints: (0, package_chunk_sort_1.generateEntryPoints)({ scripts, styles }),
|
|
36
|
+
files: this.filterAndMapBuildFiles(files, ['.js', '.css']),
|
|
37
|
+
moduleFiles: this.filterAndMapBuildFiles(moduleFiles, ['.js']),
|
|
38
|
+
loadOutputFile: (filePath) => compilation.assets[filePath].source().toString(),
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
getEmittedFiles(compilation) {
|
|
44
|
+
const files = [];
|
|
45
|
+
// adds all chunks to the list of emitted files such as lazy loaded modules
|
|
46
|
+
for (const chunk of compilation.chunks) {
|
|
47
|
+
for (const file of chunk.files) {
|
|
48
|
+
files.push({
|
|
49
|
+
// The id is guaranteed to exist at this point in the compilation process
|
|
50
|
+
id: chunk.id.toString(),
|
|
51
|
+
name: chunk.name,
|
|
52
|
+
file,
|
|
53
|
+
extension: (0, path_1.extname)(file),
|
|
54
|
+
initial: chunk.isOnlyInitial(),
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// other all files
|
|
59
|
+
for (const file of Object.keys(compilation.assets)) {
|
|
60
|
+
files.push({
|
|
61
|
+
file,
|
|
62
|
+
extension: (0, path_1.extname)(file),
|
|
63
|
+
initial: false,
|
|
64
|
+
asset: true,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
// dedupe
|
|
68
|
+
return files.filter(({ file, name }, index) => files.findIndex((f) => f.file === file && (!name || name === f.name)) === index);
|
|
69
|
+
}
|
|
70
|
+
stripBom(data) {
|
|
71
|
+
return data.replace(/^\uFEFF/, '');
|
|
72
|
+
}
|
|
73
|
+
augmentIndexHtml(params) {
|
|
74
|
+
const { loadOutputFile, files, moduleFiles = [], entrypoints } = params;
|
|
75
|
+
let { crossOrigin = 'none' } = params;
|
|
76
|
+
if (params.sri && crossOrigin === 'none') {
|
|
77
|
+
crossOrigin = 'anonymous';
|
|
78
|
+
}
|
|
79
|
+
const stylesheets = new Set();
|
|
80
|
+
const scripts = new Set();
|
|
81
|
+
// Sort files in the order we want to insert them by entrypoint and dedupes duplicates
|
|
82
|
+
const mergedFiles = [...moduleFiles, ...files];
|
|
83
|
+
for (const entrypoint of entrypoints) {
|
|
84
|
+
for (const { extension, file, name } of mergedFiles) {
|
|
85
|
+
if (name !== entrypoint) {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
switch (extension) {
|
|
89
|
+
case '.js':
|
|
90
|
+
scripts.add(file);
|
|
91
|
+
break;
|
|
92
|
+
case '.css':
|
|
93
|
+
stylesheets.add(file);
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// Find the head and body elements
|
|
99
|
+
const treeAdapter = parse5.treeAdapters.default;
|
|
100
|
+
const document = parse5.parse(params.inputContent, {
|
|
101
|
+
treeAdapter,
|
|
102
|
+
locationInfo: true,
|
|
103
|
+
});
|
|
104
|
+
let headElement;
|
|
105
|
+
let bodyElement;
|
|
106
|
+
for (const docChild of document.childNodes) {
|
|
107
|
+
if (docChild.tagName === 'html') {
|
|
108
|
+
for (const htmlChild of docChild.childNodes) {
|
|
109
|
+
if (htmlChild.tagName === 'head') {
|
|
110
|
+
headElement = htmlChild;
|
|
111
|
+
}
|
|
112
|
+
else if (htmlChild.tagName === 'body') {
|
|
113
|
+
bodyElement = htmlChild;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (!headElement || !bodyElement) {
|
|
119
|
+
throw new Error('Missing head and/or body elements');
|
|
120
|
+
}
|
|
121
|
+
// Determine script insertion point
|
|
122
|
+
let scriptInsertionPoint;
|
|
123
|
+
if (bodyElement.__location && bodyElement.__location.endTag) {
|
|
124
|
+
scriptInsertionPoint = bodyElement.__location.endTag.startOffset;
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
// Less accurate fallback
|
|
128
|
+
// parse5 4.x does not provide locations if malformed html is present
|
|
129
|
+
scriptInsertionPoint = params.inputContent.indexOf('</body>');
|
|
130
|
+
}
|
|
131
|
+
let styleInsertionPoint;
|
|
132
|
+
if (headElement.__location && headElement.__location.endTag) {
|
|
133
|
+
styleInsertionPoint = headElement.__location.endTag.startOffset;
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
// Less accurate fallback
|
|
137
|
+
// parse5 4.x does not provide locations if malformed html is present
|
|
138
|
+
styleInsertionPoint = params.inputContent.indexOf('</head>');
|
|
139
|
+
}
|
|
140
|
+
// Inject into the html
|
|
141
|
+
const indexSource = new rspack.sources.ReplaceSource(new rspack.sources.RawSource(params.inputContent), params.input);
|
|
142
|
+
let scriptElements = '';
|
|
143
|
+
for (const script of scripts) {
|
|
144
|
+
const attrs = [
|
|
145
|
+
{ name: 'src', value: (params.deployUrl || '') + script },
|
|
146
|
+
];
|
|
147
|
+
if (crossOrigin !== 'none') {
|
|
148
|
+
attrs.push({ name: 'crossorigin', value: crossOrigin });
|
|
149
|
+
}
|
|
150
|
+
// We want to include nomodule or module when a file is not common amongs all
|
|
151
|
+
// such as runtime.js
|
|
152
|
+
const scriptPredictor = ({ file, }) => file === script;
|
|
153
|
+
if (!files.some(scriptPredictor)) {
|
|
154
|
+
// in some cases for differential loading file with the same name is avialable in both
|
|
155
|
+
// nomodule and module such as scripts.js
|
|
156
|
+
// we shall not add these attributes if that's the case
|
|
157
|
+
const isModuleType = moduleFiles.some(scriptPredictor);
|
|
158
|
+
if (isModuleType) {
|
|
159
|
+
attrs.push({ name: 'type', value: 'module' });
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
attrs.push({ name: 'defer', value: null });
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
attrs.push({ name: 'type', value: 'module' });
|
|
167
|
+
}
|
|
168
|
+
if (params.sri) {
|
|
169
|
+
const content = loadOutputFile(script);
|
|
170
|
+
attrs.push(...this.generateSriAttributes(content));
|
|
171
|
+
}
|
|
172
|
+
const attributes = attrs
|
|
173
|
+
.map((attr) => attr.value === null ? attr.name : `${attr.name}="${attr.value}"`)
|
|
174
|
+
.join(' ');
|
|
175
|
+
scriptElements += `<script ${attributes}></script>`;
|
|
176
|
+
}
|
|
177
|
+
indexSource.insert(scriptInsertionPoint, scriptElements);
|
|
178
|
+
// Adjust base href if specified
|
|
179
|
+
if (typeof params.baseHref == 'string') {
|
|
180
|
+
let baseElement;
|
|
181
|
+
for (const headChild of headElement.childNodes) {
|
|
182
|
+
if (headChild.tagName === 'base') {
|
|
183
|
+
baseElement = headChild;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
const baseFragment = treeAdapter.createDocumentFragment();
|
|
187
|
+
if (!baseElement) {
|
|
188
|
+
baseElement = treeAdapter.createElement('base', undefined, [
|
|
189
|
+
{ name: 'href', value: params.baseHref },
|
|
190
|
+
]);
|
|
191
|
+
treeAdapter.appendChild(baseFragment, baseElement);
|
|
192
|
+
indexSource.insert(headElement.__location.startTag.endOffset, parse5.serialize(baseFragment, { treeAdapter }));
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
let hrefAttribute;
|
|
196
|
+
for (const attribute of baseElement.attrs) {
|
|
197
|
+
if (attribute.name === 'href') {
|
|
198
|
+
hrefAttribute = attribute;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (hrefAttribute) {
|
|
202
|
+
hrefAttribute.value = params.baseHref;
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
baseElement.attrs.push({ name: 'href', value: params.baseHref });
|
|
206
|
+
}
|
|
207
|
+
treeAdapter.appendChild(baseFragment, baseElement);
|
|
208
|
+
indexSource.replace(baseElement.__location.startOffset, baseElement.__location.endOffset, parse5.serialize(baseFragment, { treeAdapter }));
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
const styleElements = treeAdapter.createDocumentFragment();
|
|
212
|
+
for (const stylesheet of stylesheets) {
|
|
213
|
+
const attrs = [
|
|
214
|
+
{ name: 'rel', value: 'stylesheet' },
|
|
215
|
+
{ name: 'href', value: (params.deployUrl || '') + stylesheet },
|
|
216
|
+
];
|
|
217
|
+
if (crossOrigin !== 'none') {
|
|
218
|
+
attrs.push({ name: 'crossorigin', value: crossOrigin });
|
|
219
|
+
}
|
|
220
|
+
if (params.sri) {
|
|
221
|
+
const content = loadOutputFile(stylesheet);
|
|
222
|
+
attrs.push(...this.generateSriAttributes(content));
|
|
223
|
+
}
|
|
224
|
+
const element = treeAdapter.createElement('link', undefined, attrs);
|
|
225
|
+
treeAdapter.appendChild(styleElements, element);
|
|
226
|
+
}
|
|
227
|
+
indexSource.insert(styleInsertionPoint, parse5.serialize(styleElements, { treeAdapter }));
|
|
228
|
+
return indexSource;
|
|
229
|
+
}
|
|
230
|
+
generateSriAttributes(content) {
|
|
231
|
+
const algo = 'sha384';
|
|
232
|
+
const hash = (0, crypto_1.createHash)(algo).update(content, 'utf8').digest('base64');
|
|
233
|
+
return [{ name: 'integrity', value: `${algo}-${hash}` }];
|
|
234
|
+
}
|
|
235
|
+
filterAndMapBuildFiles(files, extensionFilter) {
|
|
236
|
+
const filteredFiles = [];
|
|
237
|
+
// This test excludes files generated by HMR (e.g. main.hot-update.js).
|
|
238
|
+
const hotUpdateAsset = /hot-update\.[cm]?js$/;
|
|
239
|
+
for (const { file, name, extension, initial } of files) {
|
|
240
|
+
if (name &&
|
|
241
|
+
initial &&
|
|
242
|
+
extensionFilter.includes(extension) &&
|
|
243
|
+
!hotUpdateAsset.test(file)) {
|
|
244
|
+
filteredFiles.push({ file, extension, name });
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return filteredFiles;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
exports.WriteIndexHtmlPlugin = WriteIndexHtmlPlugin;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function interpolateEnvironmentVariablesToIndex(contents: string, deployUrl?: string): string;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.interpolateEnvironmentVariablesToIndex = interpolateEnvironmentVariablesToIndex;
|
|
4
|
+
function interpolateEnvironmentVariablesToIndex(contents, deployUrl) {
|
|
5
|
+
const environmentVariables = getClientEnvironment(deployUrl || '');
|
|
6
|
+
return interpolateEnvironmentVariables(contents, environmentVariables);
|
|
7
|
+
}
|
|
8
|
+
const NX_PREFIX = /^NX_PUBLIC_/i;
|
|
9
|
+
function isNxEnvironmentKey(x) {
|
|
10
|
+
return NX_PREFIX.test(x);
|
|
11
|
+
}
|
|
12
|
+
function getClientEnvironment(deployUrl) {
|
|
13
|
+
return Object.keys(process.env)
|
|
14
|
+
.filter(isNxEnvironmentKey)
|
|
15
|
+
.reduce((env, key) => {
|
|
16
|
+
env[key] = process.env[key];
|
|
17
|
+
return env;
|
|
18
|
+
}, {
|
|
19
|
+
NODE_ENV: process.env.NODE_ENV || 'development',
|
|
20
|
+
DEPLOY_URL: deployUrl || process.env.DEPLOY_URL || '',
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
function interpolateEnvironmentVariables(documentContents, environmentVariables) {
|
|
24
|
+
let temp = documentContents;
|
|
25
|
+
for (const [key, value] of Object.entries(environmentVariables)) {
|
|
26
|
+
temp = temp.replace(new RegExp(`%${key}%`, 'g'), value);
|
|
27
|
+
}
|
|
28
|
+
return temp;
|
|
29
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.normalizeExtraEntryPoints = normalizeExtraEntryPoints;
|
|
4
|
+
function normalizeExtraEntryPoints(extraEntryPoints, defaultBundleName) {
|
|
5
|
+
return extraEntryPoints.map((entry) => {
|
|
6
|
+
let normalizedEntry;
|
|
7
|
+
if (typeof entry === 'string') {
|
|
8
|
+
normalizedEntry = {
|
|
9
|
+
input: entry,
|
|
10
|
+
inject: true,
|
|
11
|
+
bundleName: defaultBundleName,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
const { inject = true, ...newEntry } = entry;
|
|
16
|
+
let bundleName;
|
|
17
|
+
if (entry.bundleName) {
|
|
18
|
+
bundleName = entry.bundleName;
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
bundleName = defaultBundleName;
|
|
22
|
+
}
|
|
23
|
+
normalizedEntry = { ...newEntry, bundleName };
|
|
24
|
+
}
|
|
25
|
+
return normalizedEntry;
|
|
26
|
+
});
|
|
27
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateEntryPoints = generateEntryPoints;
|
|
4
|
+
const normalize_entry_1 = require("./normalize-entry");
|
|
5
|
+
function generateEntryPoints(appConfig) {
|
|
6
|
+
// Add all styles/scripts, except lazy-loaded ones.
|
|
7
|
+
const extraEntryPoints = (extraEntryPoints, defaultBundleName) => {
|
|
8
|
+
const entryPoints = (0, normalize_entry_1.normalizeExtraEntryPoints)(extraEntryPoints, defaultBundleName).map((entry) => entry.bundleName);
|
|
9
|
+
// remove duplicates
|
|
10
|
+
return [...new Set(entryPoints)];
|
|
11
|
+
};
|
|
12
|
+
const styleEntryPoints = appConfig.styles.filter((style) => !(typeof style !== 'string' && !style.inject));
|
|
13
|
+
const scriptEntryPoints = appConfig.scripts.filter((script) => !(typeof script !== 'string' && !script.inject));
|
|
14
|
+
const entryPoints = [
|
|
15
|
+
'runtime',
|
|
16
|
+
'polyfills',
|
|
17
|
+
'sw-register',
|
|
18
|
+
...extraEntryPoints(styleEntryPoints, 'styles'),
|
|
19
|
+
...extraEntryPoints(scriptEntryPoints, 'scripts'),
|
|
20
|
+
'vendor',
|
|
21
|
+
'main',
|
|
22
|
+
];
|
|
23
|
+
const duplicates = [
|
|
24
|
+
...new Set(entryPoints.filter((x) => entryPoints.indexOf(x) !== entryPoints.lastIndexOf(x))),
|
|
25
|
+
];
|
|
26
|
+
if (duplicates.length > 0) {
|
|
27
|
+
throw new Error(`Multiple bundles have been named the same: '${duplicates.join(`', '`)}'.`);
|
|
28
|
+
}
|
|
29
|
+
return entryPoints;
|
|
30
|
+
}
|
package/src/utils/with-nx.js
CHANGED
|
@@ -26,6 +26,7 @@ function withNx(pluginOptions = {}) {
|
|
|
26
26
|
targetName: context.targetName,
|
|
27
27
|
configurationName: context.configurationName,
|
|
28
28
|
projectGraph: context.projectGraph,
|
|
29
|
+
useLegacyHtmlPlugin: pluginOptions.useLegacyHtmlPlugin ?? false,
|
|
29
30
|
}, config);
|
|
30
31
|
processed.add(config);
|
|
31
32
|
return config;
|
package/src/utils/with-web.d.ts
CHANGED
|
@@ -18,5 +18,9 @@ export interface WithWebOptions {
|
|
|
18
18
|
};
|
|
19
19
|
cssModules?: boolean;
|
|
20
20
|
ssr?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Use the legacy WriteIndexHtmlPlugin instead of the built-in HtmlRspackPlugin.
|
|
23
|
+
*/
|
|
24
|
+
useLegacyHtmlPlugin?: boolean;
|
|
21
25
|
}
|
|
22
26
|
export declare function withWeb(pluginOptions?: WithWebOptions): (config: Configuration, { options, context }: NxRspackExecutionContext) => Configuration;
|
package/src/utils/with-web.js
CHANGED
|
@@ -16,6 +16,7 @@ function withWeb(pluginOptions = {}) {
|
|
|
16
16
|
targetName: context.targetName,
|
|
17
17
|
configurationName: context.configurationName,
|
|
18
18
|
projectGraph: context.projectGraph,
|
|
19
|
+
useLegacyHtmlPlugin: pluginOptions.useLegacyHtmlPlugin ?? false,
|
|
19
20
|
}, config);
|
|
20
21
|
processed.add(config);
|
|
21
22
|
return config;
|