microsoft/typespec

Public

mirrored from https://github.com/microsoft/typespecAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
1b74b19bc12c226dcd0f3fcd4eab6d66babbc2a8

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

packages/bundler/src/vite-plugin.ts

135lines · modecode

1import { resolvePath } from "@cadl-lang/compiler";
2import { resolve } from "path";
3import type { IndexHtmlTransformContext, Plugin, ResolvedConfig } from "vite";
4import { CadlBundle, CadlBundleDefinition, createCadlBundle, watchCadlBundle } from "./bundler.js";
5
6export interface CadlBundlePluginOptions {
7 folderName: string;
8
9 /**
10 * Name of libraries to bundle.
11 */
12 libraries: string[];
13}
14
15export function cadlBundlePlugin(options: CadlBundlePluginOptions): Plugin {
16 let config: ResolvedConfig;
17 const definitions: Record<string, CadlBundleDefinition> = {};
18 const bundles: Record<string, CadlBundle> = {};
19
20 return {
21 name: "cadl-bundle",
22 enforce: "pre",
23 async configResolved(c) {
24 config = c;
25 },
26 async buildStart() {
27 for (const name of options.libraries) {
28 const bundle = await bundleLibrary(config.root, name);
29 bundles[name] = bundle;
30 definitions[name] = bundle.definition;
31 }
32 },
33 async configureServer(server) {
34 for (const library of options.libraries) {
35 await watchBundleLibrary(config.root, library, (bundle) => {
36 bundles[library] = bundle;
37 definitions[library] = bundle.definition;
38 server.ws.send({ type: "full-reload" });
39 });
40 }
41
42 server.middlewares.use((req, res, next) => {
43 const id = req.url;
44 if (id === undefined) {
45 next();
46 return;
47 }
48 const start = `/${options.folderName}/`;
49
50 const resolveFilename = (path: string) => {
51 if (path === "") {
52 return "index.js";
53 } else {
54 return `${path}.js`;
55 }
56 };
57 const findPkgName = (id: string): [string, string] | undefined => {
58 const segments = id.slice(start.length, -".js".length).split("/");
59 if (bundles[segments[0]]) {
60 return [segments[0], resolveFilename(segments.slice(1).join("/"))];
61 }
62 const inFolder = segments[0] + "/" + segments[1];
63 if (bundles[inFolder]) {
64 return [inFolder, resolveFilename(segments.slice(2).join("/"))];
65 }
66 return undefined;
67 };
68 if (id.startsWith(start) && id.endsWith(".js")) {
69 const found = findPkgName(id);
70 if (found) {
71 const [pkgId, path] = found;
72 const file = bundles[pkgId].files.find((x) => x.filename === path);
73 if (file) {
74 res.writeHead(200, "Ok", { "Content-Type": "application/javascript" });
75 res.write(file.content);
76 res.end();
77 return;
78 }
79 }
80 }
81 next();
82 });
83 },
84
85 async generateBundle() {
86 for (const name of options.libraries) {
87 for (const file of bundles[name].files) {
88 this.emitFile({
89 type: "asset",
90 fileName: `${options.folderName}/${name}/${file.filename}`,
91 source: file.content,
92 });
93 }
94 }
95 },
96
97 transformIndexHtml: {
98 enforce: "post",
99 transform: (html: string, ctx: IndexHtmlTransformContext) => {
100 // Inject the importmap before the html script. Cannot just use injectTo:head-prepend as vite will inject its own script before that and cause a failure.
101 const importMapTag = `<script type="importmap">\n${JSON.stringify(
102 createImportMap(options.folderName, definitions),
103 null,
104 2
105 )}\n</script>`;
106 return html.replace("<html", importMapTag + "\n<html");
107 },
108 },
109 };
110}
111
112function createImportMap(folderName: string, definitions: Record<string, CadlBundleDefinition>) {
113 const imports: Record<string, string> = {};
114 for (const [library, definition] of Object.entries(definitions)) {
115 imports[library] = `./${folderName}/${library}/index.js`;
116 for (const name of Object.keys(definition.exports)) {
117 imports[library + "/http"] = "./" + resolvePath(`./${folderName}/${library}`, name) + ".js";
118 }
119 }
120 const importMap = {
121 imports: imports,
122 };
123
124 return importMap;
125}
126async function bundleLibrary(projectRoot: string, name: string) {
127 return await createCadlBundle(resolve(projectRoot, "node_modules", name));
128}
129async function watchBundleLibrary(
130 projectRoot: string,
131 name: string,
132 onChange: (bundle: CadlBundle) => void
133) {
134 return await watchCadlBundle(resolve(projectRoot, "node_modules", name), onChange);
135}
136