diff --git a/eng/liveBuilds.targets b/eng/liveBuilds.targets index 5ea1ca5525a2a..855ad32118d3d 100644 --- a/eng/liveBuilds.targets +++ b/eng/liveBuilds.targets @@ -195,8 +195,10 @@ + + diff --git a/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets b/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets index 5cf78e89c3c15..4186a7a43b17b 100644 --- a/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets +++ b/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets @@ -208,6 +208,11 @@ Copyright (c) .NET Foundation. All rights reserved. Condition="@(WasmNativeAsset->Count()) > 0 and ( '%(FileName)' == 'dotnet' or '%(FileName)' == 'dotnet.native' ) and ('%(Extension)' == '.wasm' or '%(Extension)' == '.js')" /> + + <_WasmEmitSourceMapBuild>$(WasmEmitSourceMap) + <_WasmEmitSourceMapBuild Condition="'$(_WasmEmitSourceMapBuild)' == ''">true + + @@ -376,6 +382,11 @@ Copyright (c) .NET Foundation. All rights reserved. Condition="'%(StaticWebAsset.AssetTraitName)' == 'WasmResource' or '%(StaticWebAsset.AssetTraitName)' == 'Culture' or '%(AssetRole)' == 'Alternative'" /> + + <_WasmEmitSourceMapPublish>$(WasmEmitSourceMap) + <_WasmEmitSourceMapPublish Condition="'$(_WasmEmitSourceMapPublish)' == ''">false + + diff --git a/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs b/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs index 8e0f23548a575..433af91ec0f16 100644 --- a/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs +++ b/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs @@ -685,8 +685,10 @@ protected static void AssertBasicAppBundle(string bundleDir, "_framework/dotnet.native.wasm", "_framework/blazor.boot.json", "_framework/dotnet.js", + "_framework/dotnet.js.map", "_framework/dotnet.native.js", - "_framework/dotnet.runtime.js" + "_framework/dotnet.runtime.js", + "_framework/dotnet.runtime.js.map", }; if (isBrowserProject) diff --git a/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/NativeRebuildTestsBase.cs b/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/NativeRebuildTestsBase.cs index d190dc2199410..7bdc4c2c45ef6 100644 --- a/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/NativeRebuildTestsBase.cs +++ b/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/NativeRebuildTestsBase.cs @@ -186,7 +186,9 @@ internal void CompareStat(IDictionary oldStat, IDictionary + + diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index 50c6324eeb0f0..b715912b04ef3 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -309,6 +309,7 @@ interface BootJsonData { readonly resources: ResourceGroups; /** Gets a value that determines if this boot config was produced from a non-published build (i.e. dotnet build or dotnet run) */ readonly debugBuild: boolean; + readonly debugLevel: number; readonly linkerEnabled: boolean; readonly cacheBootResources: boolean; readonly config: string[]; diff --git a/src/mono/wasm/runtime/package.json b/src/mono/wasm/runtime/package.json index 249235b29c5ff..4f247bff7cea4 100644 --- a/src/mono/wasm/runtime/package.json +++ b/src/mono/wasm/runtime/package.json @@ -22,11 +22,12 @@ "author": "Microsoft", "license": "MIT", "devDependencies": { + "@rollup/plugin-terser": "0.4.1", "@rollup/plugin-typescript": "11.1.0", "@rollup/plugin-virtual": "3.0.1", - "@rollup/plugin-terser": "0.4.1", "@typescript-eslint/eslint-plugin": "5.59.1", "@typescript-eslint/parser": "5.59.1", + "magic-string": "0.30.0", "eslint": "8.39.0", "fast-glob": "3.2.12", "git-commit-info": "2.0.1", diff --git a/src/mono/wasm/runtime/rollup.config.js b/src/mono/wasm/runtime/rollup.config.js index 9d1074488e1cd..dd4b704d857b9 100644 --- a/src/mono/wasm/runtime/rollup.config.js +++ b/src/mono/wasm/runtime/rollup.config.js @@ -10,9 +10,11 @@ import dts from "rollup-plugin-dts"; import { createFilter } from "@rollup/pluginutils"; import fast_glob from "fast-glob"; import gitCommitInfo from "git-commit-info"; +import MagicString from "magic-string"; const configuration = process.env.Configuration; const isDebug = configuration !== "Release"; +const isContinuousIntegrationBuild = process.env.ContinuousIntegrationBuild === "true" ? true : false; const productVersion = process.env.ProductVersion || "8.0.0-dev"; const nativeBinDir = process.env.NativeBinDir ? process.env.NativeBinDir.replace(/"/g, "") : "bin"; const monoWasmThreads = process.env.MonoWasmThreads === "true" ? true : false; @@ -38,13 +40,15 @@ const banner_dts = banner + "//!\n//! This is generated file, see src/mono/wasm/ // emcc doesn't know how to load ES6 module, that's why we need the whole rollup.js const inlineAssert = [ { - pattern: /mono_assert\(([^,]*), *"([^"]*)"\);/gm, // eslint-disable-next-line quotes - replacement: 'if (!($1)) throw new Error("Assert failed: $2"); // inlined mono_assert' + pattern: 'mono_assert\\(([^,]*), *"([^"]*)"\\);', + // eslint-disable-next-line quotes + replacement: (match) => `if (!(${match[1]})) throw new Error("Assert failed: ${match[2]}"); // inlined mono_assert` }, { - pattern: /mono_assert\(([^,]*), \(\) => *`([^`]*)`\);/gm, - replacement: "if (!($1)) throw new Error(`Assert failed: $2`); // inlined mono_assert" + // eslint-disable-next-line quotes + pattern: 'mono_assert\\(([^,]*), \\(\\) => *`([^`]*)`\\);', + replacement: (match) => `if (!(${match[1]})) throw new Error(\`Assert failed: ${match[2]}\`); // inlined mono_assert` } ]; const checkAssert = @@ -78,8 +82,28 @@ const envConstants = { monoDiagnosticsMock, gitHash, wasmEnableLegacyJsInterop, + isContinuousIntegrationBuild, }; +const locationCache = {}; +function sourcemapPathTransform(relativeSourcePath, sourcemapPath) { + let res = locationCache[relativeSourcePath]; + if (res === undefined) { + if (!isContinuousIntegrationBuild) { + const sourcePath = path.resolve( + path.dirname(sourcemapPath), + relativeSourcePath + ); + res = `file:///${sourcePath.replace(/\\/g, "/")}`; + } else { + relativeSourcePath = relativeSourcePath.substring(12); + res = `https://raw.githubusercontent.com/dotnet/runtime/${gitHash}/${relativeSourcePath}`; + } + locationCache[relativeSourcePath] = res; + } + return res; +} + function consts(dict) { // implement rollup-plugin-const in terms of @rollup/plugin-virtual // It's basically the same thing except "consts" names all its modules with a "consts:" prefix, @@ -103,7 +127,7 @@ const typescriptConfigOptions = { }; const outputCodePlugins = [consts(envConstants), typescript(typescriptConfigOptions)]; -const externalDependencies = ["module"]; +const externalDependencies = ["module", "process"]; const loaderConfig = { treeshake: !isDebug, @@ -114,25 +138,14 @@ const loaderConfig = { file: nativeBinDir + "/dotnet.js", banner, plugins, + sourcemap: true, + sourcemapPathTransform, } ], external: externalDependencies, plugins: [regexReplace(inlineAssert), regexCheck([checkAssert, checkNoRuntime]), ...outputCodePlugins], onwarn: onwarn }; -const typesConfig = { - input: "./types/export-types.ts", - output: [ - { - format: "es", - file: nativeBinDir + "/dotnet.d.ts", - banner: banner_dts, - plugins: [writeOnChangePlugin()], - } - ], - external: externalDependencies, - plugins: [dts()], -}; const runtimeConfig = { treeshake: !isDebug, input: "exports.ts", @@ -142,13 +155,28 @@ const runtimeConfig = { file: nativeBinDir + "/dotnet.runtime.js", banner, plugins, + sourcemap: true, + sourcemapPathTransform, } ], external: externalDependencies, plugins: [regexReplace(inlineAssert), regexCheck([checkAssert, checkNoLoader]), ...outputCodePlugins], onwarn: onwarn }; -const legacyConfig = { +const typesConfig = { + input: "./types/export-types.ts", + output: [ + { + format: "es", + file: nativeBinDir + "/dotnet.d.ts", + banner: banner_dts, + plugins: [writeOnChangePlugin()], + } + ], + external: externalDependencies, + plugins: [dts()], +}; +const legacyTypesConfig = { input: "./net6-legacy/export-types.ts", output: [ { @@ -174,7 +202,7 @@ if (isDebug) { banner: banner_dts, plugins: [alwaysLF(), writeOnChangePlugin()], }); - legacyConfig.output.push({ + legacyTypesConfig.output.push({ format: "es", file: "./dotnet-legacy.d.ts", banner: banner_dts, @@ -221,7 +249,7 @@ const allConfigs = [ loaderConfig, runtimeConfig, typesConfig, - legacyConfig, + legacyTypesConfig, ].concat(workerConfigs) .concat(diagnosticMockTypesConfig ? [diagnosticMockTypesConfig] : []); export default defineConfig(allConfigs); @@ -336,19 +364,34 @@ function regexReplace(replacements = []) { } }; - function executeReplacement(_, code) { - // TODO use MagicString for sourcemap support - let fixed = code; - for (const rep of replacements) { - const { pattern, replacement } = rep; - fixed = fixed.replace(pattern, replacement); + function executeReplacement(_, code, id) { + const magicString = new MagicString(code); + if (!codeHasReplacements(code, id, magicString)) { + return null; } - if (fixed == code) { - return null; + const result = { code: magicString.toString() }; + result.map = magicString.generateMap({ hires: true }); + return result; + } + + function codeHasReplacements(code, id, magicString) { + let result = false; + let match; + for (const rep of replacements) { + const { pattern, replacement } = rep; + const rx = new RegExp(pattern, "gm"); + while ((match = rx.exec(code))) { + result = true; + const updated = replacement(match); + const start = match.index; + const end = start + match[0].length; + magicString.overwrite(start, end, updated); + } } - return { code: fixed }; + // eslint-disable-next-line no-cond-assign + return result; } } diff --git a/src/mono/wasm/runtime/tsconfig.json b/src/mono/wasm/runtime/tsconfig.json index 4353fe7e54bcb..c5a5693168aa2 100644 --- a/src/mono/wasm/runtime/tsconfig.json +++ b/src/mono/wasm/runtime/tsconfig.json @@ -5,5 +5,6 @@ "esnext", "dom" ], + "sourceMap": true, } -} +} \ No newline at end of file diff --git a/src/mono/wasm/wasm.proj b/src/mono/wasm/wasm.proj index c150a1fe09d31..2842fb19f2507 100644 --- a/src/mono/wasm/wasm.proj +++ b/src/mono/wasm/wasm.proj @@ -451,7 +451,9 @@ - Configuration:$(Configuration),NativeBinDir:$(NativeBinDir),ProductVersion:$(ProductVersion),MonoWasmThreads:$(MonoWasmThreads),DISABLE_LEGACY_JS_INTEROP:$(_DisableLegacyJsInterop),MonoDiagnosticsMock:$(MonoDiagnosticsMock) + Configuration:$(Configuration),NativeBinDir:$(NativeBinDir),ProductVersion:$(ProductVersion),MonoWasmThreads:$(MonoWasmThreads),DISABLE_LEGACY_JS_INTEROP:$(_DisableLegacyJsInterop),MonoDiagnosticsMock:$(MonoDiagnosticsMock),ContinuousIntegrationBuild:$(ContinuousIntegrationBuild) diff --git a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/AssetsComputingHelper.cs b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/AssetsComputingHelper.cs index e102f669d477f..445f427e02faf 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/AssetsComputingHelper.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/AssetsComputingHelper.cs @@ -32,6 +32,7 @@ public static bool ShouldFilterCandidate( bool copySymbols, string customIcuCandidateFilename, bool enableThreads, + bool emitSourceMap, out string reason) { var extension = candidate.GetMetadata("Extension"); @@ -55,6 +56,7 @@ public static bool ShouldFilterCandidate( ".dat" when !string.IsNullOrEmpty(customIcuCandidateFilename) && fileName != customIcuCandidateFilename => "custom icu file will be used instead of icu from the runtime pack", ".json" when fromMonoPackage && (fileName == "emcc-props" || fileName == "package") => $"{fileName}{extension} is not used by Blazor", ".ts" when fromMonoPackage && fileName == "dotnet.d" => "dotnet type definition is not used by Blazor", + ".map" when !emitSourceMap && fromMonoPackage && (fileName == "dotnet.js" || fileName == "dotnet.runtime.js") => "source map file is not published", ".ts" when fromMonoPackage && fileName == "dotnet-legacy.d" => "dotnet type definition is not used by Blazor", ".js" when assetType == "native" && !(dotnetJsSingleThreadNames.Contains(fileName) || (enableThreads && fileName == "dotnet.native.worker")) => $"{fileName}{extension} is not used by Blazor", ".pdb" when !copySymbols => "copying symbols is disabled", diff --git a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ComputeWasmBuildAssets.cs b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ComputeWasmBuildAssets.cs index 580f5288b2ccb..ba25c5103c4d5 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ComputeWasmBuildAssets.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ComputeWasmBuildAssets.cs @@ -50,6 +50,8 @@ public class ComputeWasmBuildAssets : Task public bool EnableThreads { get; set; } + public bool EmitSourceMap { get; set; } + [Output] public ITaskItem[] AssetCandidates { get; set; } @@ -84,7 +86,7 @@ public override bool Execute() for (int i = 0; i < Candidates.Length; i++) { var candidate = Candidates[i]; - if (AssetsComputingHelper.ShouldFilterCandidate(candidate, TimeZoneSupport, InvariantGlobalization, CopySymbols, customIcuCandidateFilename, EnableThreads, out var reason)) + if (AssetsComputingHelper.ShouldFilterCandidate(candidate, TimeZoneSupport, InvariantGlobalization, CopySymbols, customIcuCandidateFilename, EnableThreads, EmitSourceMap, out var reason)) { Log.LogMessage(MessageImportance.Low, "Skipping asset '{0}' because '{1}'", candidate.ItemSpec, reason); filesToRemove.Add(candidate); diff --git a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ComputeWasmPublishAssets.cs b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ComputeWasmPublishAssets.cs index dd9075435c3c9..8a9bcc500dadc 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ComputeWasmPublishAssets.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ComputeWasmPublishAssets.cs @@ -58,6 +58,8 @@ public class ComputeWasmPublishAssets : Task public bool EnableThreads { get; set; } + public bool EmitSourceMap { get; set; } + public bool IsWebCilEnabled { get; set; } [Output] @@ -575,7 +577,7 @@ private void GroupResolvedFilesToPublish( foreach (var candidate in resolvedFilesToPublish) { - if (AssetsComputingHelper.ShouldFilterCandidate(candidate, TimeZoneSupport, InvariantGlobalization, CopySymbols, customIcuCandidateFilename, EnableThreads, out var reason)) + if (AssetsComputingHelper.ShouldFilterCandidate(candidate, TimeZoneSupport, InvariantGlobalization, CopySymbols, customIcuCandidateFilename, EnableThreads, EmitSourceMap, out var reason)) { Log.LogMessage(MessageImportance.Low, "Skipping asset '{0}' because '{1}'", candidate.ItemSpec, reason); if (!resolvedFilesToPublishToRemove.ContainsKey(candidate.ItemSpec)) diff --git a/src/tasks/WasmAppBuilder/WasmAppBuilder.cs b/src/tasks/WasmAppBuilder/WasmAppBuilder.cs index 88cf5faf255f3..12e7cb3a581c7 100644 --- a/src/tasks/WasmAppBuilder/WasmAppBuilder.cs +++ b/src/tasks/WasmAppBuilder/WasmAppBuilder.cs @@ -150,6 +150,12 @@ protected override bool ExecuteInternal() if (!IncludeThreadsWorker && name == "dotnet.native.worker.js") continue; + if (name == "dotnet.runtime.js.map" || name == "dotnet.js.map") + { + Log.LogMessage(MessageImportance.Low, $"Skipping {item.ItemSpec} from boot config"); + continue; + } + var itemHash = Utils.ComputeIntegrity(item.ItemSpec); if (name.StartsWith("dotnet", StringComparison.OrdinalIgnoreCase) && string.Equals(Path.GetExtension(name), ".wasm", StringComparison.OrdinalIgnoreCase))