1+ {-# LANGUAGE OverloadedRecordDot #-}
2+
13module Tests.SnapshotTests.WaspBuildSnapshotTest (waspBuildSnapshotTest ) where
24
5+ import Control.Monad.Reader (asks )
6+ import qualified Data.Text as T
7+ import NeatInterpolation (trimming )
38import ShellCommands
4- ( buildAndRemoveWaspProjectDockerImage ,
9+ ( ShellCommand ,
10+ ShellCommandBuilder ,
11+ WaspProjectContext (),
12+ buildAndRemoveWaspProjectDockerImage ,
13+ createFile ,
514 createSnapshotWaspProjectFromMinimalStarter ,
615 inSnapshotWaspProjectDir ,
716 setWaspDbToPSQL ,
817 waspCliBuild ,
918 )
19+ import qualified ShellCommands as WaspProjectContext
1020import SnapshotTest (SnapshotTest , makeSnapshotTest )
21+ import StrongPath (relfile , (</>) )
22+ import qualified StrongPath as SP
23+ import qualified StrongPath.FilePath as FP
24+ import Wasp.Project.Common (WaspProjectDir )
1125
1226waspBuildSnapshotTest :: SnapshotTest
1327waspBuildSnapshotTest =
@@ -17,6 +31,82 @@ waspBuildSnapshotTest =
1731 inSnapshotWaspProjectDir
1832 [ setWaspDbToPSQL,
1933 waspCliBuild,
20- buildAndRemoveWaspProjectDockerImage
34+ buildAndRemoveWaspProjectDockerImage,
35+ wrapViteConfigForDeterministicBuild,
36+ viteBuild
2137 ]
2238 ]
39+
40+ -- | Renames the generated vite.config.ts and wraps it with a config that adds
41+ -- deterministic build options (no minification, no hashes, externalized deps),
42+ -- so the snapshot output is stable and easy to diff.
43+ wrapViteConfigForDeterministicBuild :: ShellCommandBuilder WaspProjectContext ShellCommand
44+ wrapViteConfigForDeterministicBuild = do
45+ waspProjectDir <- asks (. waspProjectDir)
46+
47+ createFile
48+ (waspProjectDir </> wrapperViteFile)
49+ [trimming |
50+ import { mergeConfig, type Plugin } from "vite";
51+ import originalConfig from "${importOriginalFromMain}";
52+
53+ export default mergeConfig(originalConfig, {
54+ plugins: [externalizeNodeModules()],
55+ build: {
56+ // Keep output readable for easier snapshot diffing.
57+ minify: false,
58+ rollupOptions: {
59+ output: {
60+ // Strip content hashes for deterministic filenames across runs.
61+ entryFileNames: "assets/[name].js",
62+ chunkFileNames: "assets/[name].js",
63+ assetFileNames: "assets/[name].[ext]",
64+ },
65+ },
66+ },
67+ });
68+
69+ // Externalize any import that resolves to node_modules,
70+ // so the build output only contains app code, for cleaner diffs.
71+ function externalizeNodeModules(): Plugin {
72+ return {
73+ name: "externalize-node-modules",
74+ enforce: "pre",
75+ async resolveId(source, importer, options) {
76+ if (!importer) return null;
77+ const resolved = await this.resolve(source, importer, {
78+ ...options,
79+ skipSelf: true,
80+ });
81+ if (resolved && resolved.id.includes("/node_modules/")) {
82+ // We externalize the module
83+ return { id: source, external: true };
84+ } else {
85+ // We let resolution proceed as normal
86+ return null;
87+ }
88+ },
89+ };
90+ }
91+ |]
92+ where
93+ importOriginalFromMain :: T. Text
94+ importOriginalFromMain = T. pack $ " ./" ++ FP. fromRelFile originalViteFile
95+
96+ viteBuild :: ShellCommandBuilder WaspProjectContext ShellCommand
97+ viteBuild =
98+ return $
99+ unwords
100+ [ " REACT_APP_API_URL=http://localhost:3001" ,
101+ " npx" ,
102+ " vite" ,
103+ " build" ,
104+ " --config" ,
105+ FP. fromRelFile wrapperViteFile
106+ ]
107+
108+ originalViteFile :: SP. Path' (SP. Rel WaspProjectDir ) SP. File'
109+ originalViteFile = [relfile |vite.config.ts|]
110+
111+ wrapperViteFile :: SP. Path' (SP. Rel WaspProjectDir ) SP. File'
112+ wrapperViteFile = [relfile |vite.config.wrapper.ts|]
0 commit comments