Skip to content

Commit 84487be

Browse files
Skryptclaude
andauthored
Add Vite minify plugin for Orchard Core asset conventions (#19023)
Adds an orchard-minify Vite plugin that produces .js/.min.js/.map and .css/.min.css/.css.map output files matching the Orchard Core asset convention. The plugin uses esbuild for JS and Lightning CSS for CSS minification. It is automatically injected by the asset manager during build and watch commands. Documentation updated accordingly. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 57b08df commit 84487be

3 files changed

Lines changed: 118 additions & 0 deletions

File tree

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { transform as esbuildTransform } from "esbuild";
2+
import { transform as lightningTransform } from "lightningcss";
3+
import { Buffer } from "buffer";
4+
import fs from "fs";
5+
import path from "path";
6+
7+
/**
8+
* Vite plugin that minifies output and generates files following the
9+
* Orchard Core asset-manager convention:
10+
*
11+
* file.js — minified WITH sourceMappingURL reference
12+
* file.min.js — minified WITHOUT sourceMappingURL reference
13+
* file.map — source map
14+
*
15+
* file.css — minified WITH sourceMappingURL reference
16+
* file.min.css — minified WITHOUT sourceMappingURL reference
17+
* file.css.map — source map
18+
*/
19+
export function minifyPlugin() {
20+
let outDir = "";
21+
22+
return {
23+
name: "orchard-minify",
24+
apply: "build",
25+
configResolved(config) {
26+
outDir = config.build.outDir;
27+
},
28+
async writeBundle(_options, bundle) {
29+
for (const [fileName, chunk] of Object.entries(bundle)) {
30+
const filePath = path.resolve(outDir, fileName);
31+
32+
if (fileName.endsWith(".js") && chunk.type === "chunk") {
33+
const parsed = path.parse(filePath);
34+
35+
const result = await esbuildTransform(chunk.code, {
36+
minify: true,
37+
sourcemap: true,
38+
sourcefile: path.basename(filePath),
39+
});
40+
41+
const mapFileName = `${parsed.name}.map`;
42+
const minPath = path.join(parsed.dir, `${parsed.name}.min.js`);
43+
44+
// .js — minified with sourcemap reference
45+
fs.writeFileSync(filePath, `${result.code}//# sourceMappingURL=${mapFileName}\n`);
46+
// .min.js — minified without sourcemap reference
47+
fs.writeFileSync(minPath, result.code);
48+
// .map — source map
49+
fs.writeFileSync(path.join(parsed.dir, mapFileName), result.map);
50+
}
51+
52+
if (fileName.endsWith(".css") && chunk.type === "asset" && typeof chunk.source === "string") {
53+
const parsed = path.parse(filePath);
54+
55+
const { code, map } = lightningTransform({
56+
code: Buffer.from(chunk.source, "utf-8"),
57+
minify: true,
58+
sourceMap: true,
59+
filename: path.basename(filePath),
60+
});
61+
62+
const mapFileName = `${parsed.name}.css.map`;
63+
const minPath = path.join(parsed.dir, `${parsed.name}.min.css`);
64+
const minified = code.toString();
65+
66+
// .css — minified with sourcemap reference
67+
fs.writeFileSync(filePath, `${minified}\n/*# sourceMappingURL=${mapFileName} */\n`);
68+
// .min.css — minified without sourcemap reference
69+
fs.writeFileSync(minPath, minified);
70+
// .css.map — source map
71+
fs.writeFileSync(path.join(parsed.dir, mapFileName), JSON.stringify(map));
72+
}
73+
}
74+
},
75+
};
76+
}

.scripts/assets-manager/vite.mjs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,18 @@ import { build, createServer } from "vite";
22
import JSON5 from "json5";
33
import { Buffer } from "buffer";
44
import process from "node:process";
5+
import { minifyPlugin } from "./plugins/vite-plugin-minify.mjs";
56

67
async function runVite(command, assetConfig) {
78
if (command === "build") {
89
await build({
910
root: assetConfig.source,
11+
plugins: [minifyPlugin()],
1012
});
1113
} else if (command === "watch") {
1214
await build({
1315
root: assetConfig.source,
16+
plugins: [minifyPlugin()],
1417
build: { watch: {} },
1518
});
1619
} else if (command === "host") {

src/docs/guides/assets-manager/README.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,45 @@ Or simply build that Vite app:
240240
yarn build -n my-vue-app
241241
```
242242
243+
#### Minification and Source Maps
244+
245+
The asset manager automatically applies the `orchard-minify` Vite plugin during `build` and `watch` commands. This plugin runs after Vite writes the bundle and produces output files that follow the Orchard Core asset convention:
246+
247+
**JavaScript:**
248+
249+
| File | Description |
250+
|------|-------------|
251+
| `file.js` | Minified with `sourceMappingURL` reference |
252+
| `file.min.js` | Minified without `sourceMappingURL` reference |
253+
| `file.map` | Source map |
254+
255+
**CSS:**
256+
257+
| File | Description |
258+
|------|-------------|
259+
| `file.css` | Minified with `sourceMappingURL` reference |
260+
| `file.min.css` | Minified without `sourceMappingURL` reference |
261+
| `file.css.map` | Source map |
262+
263+
The `.min.*` files are intended for production use (via `SetUrl()` in `ResourceManifestOptionsConfiguration`) since they do not reference a source map. The non-min files include the source map reference, making them suitable for development and debugging.
264+
265+
**How it works:**
266+
267+
- JavaScript is minified using [esbuild](https://esbuild.github.io/).
268+
- CSS is minified using [Lightning CSS](https://lightningcss.dev/).
269+
- The plugin is injected automatically by the asset manager — no configuration is needed in your `vite.config.ts`.
270+
271+
**Important:** Because the plugin disables Vite's built-in minification and handles it in a post-build step, you should **not** set `build.minify` in your `vite.config.ts` when using the asset manager. The plugin will take care of it.
272+
273+
**Resource manifest example:**
274+
275+
```csharp
276+
_manifest
277+
.DefineScript("my-app")
278+
.SetUrl("~/MyModule/Scripts/my-app.min.js", "~/MyModule/Scripts/my-app.js")
279+
.SetVersion("1.0.0");
280+
```
281+
243282
### Webpack
244283
245284
Webpack bundler action will support any configuration. From bundling a vue app to compiling a simple library. It is working by configuration file. The asset management tool simply loads a given webpack.config.js file that we instruct to use from the Assets.json file.

0 commit comments

Comments
 (0)