mirror of
https://github.com/oven-sh/bun
synced 2026-02-16 22:01:47 +00:00
Compare commits
3 Commits
claude/fix
...
don/feat/b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1537dc9635 | ||
|
|
1d32839398 | ||
|
|
1ee4fd6fb1 |
34
packages/bun-plugin-vue/.gitignore
vendored
Normal file
34
packages/bun-plugin-vue/.gitignore
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
# dependencies (bun install)
|
||||
node_modules
|
||||
|
||||
# output
|
||||
out
|
||||
dist
|
||||
*.tgz
|
||||
|
||||
# code coverage
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# logs
|
||||
logs
|
||||
_.log
|
||||
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
||||
|
||||
# dotenv environment variable files
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# caches
|
||||
.eslintcache
|
||||
.cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# IntelliJ based IDEs
|
||||
.idea
|
||||
|
||||
# Finder (MacOS) folder config
|
||||
.DS_Store
|
||||
15
packages/bun-plugin-vue/README.md
Normal file
15
packages/bun-plugin-vue/README.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# bun-plugin-vue
|
||||
|
||||
To install dependencies:
|
||||
|
||||
```bash
|
||||
bun install
|
||||
```
|
||||
|
||||
To run:
|
||||
|
||||
```bash
|
||||
bun run index.ts
|
||||
```
|
||||
|
||||
This project was created using `bun init` in bun v1.2.5. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.
|
||||
76
packages/bun-plugin-vue/bun.lock
Normal file
76
packages/bun-plugin-vue/bun.lock
Normal file
@@ -0,0 +1,76 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "bun-plugin-vue",
|
||||
"dependencies": {
|
||||
"@vue/compiler-sfc": "^3.5.0",
|
||||
},
|
||||
"devDependencies": {
|
||||
"bun-types": "canary",
|
||||
"vue": "^3.5.13",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5",
|
||||
"vue": "^3",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@babel/helper-string-parser": ["@babel/helper-string-parser@7.25.9", "", {}, "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA=="],
|
||||
|
||||
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.25.9", "", {}, "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ=="],
|
||||
|
||||
"@babel/parser": ["@babel/parser@7.26.9", "", { "dependencies": { "@babel/types": "^7.26.9" }, "bin": "./bin/babel-parser.js" }, "sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A=="],
|
||||
|
||||
"@babel/types": ["@babel/types@7.26.9", "", { "dependencies": { "@babel/helper-string-parser": "^7.25.9", "@babel/helper-validator-identifier": "^7.25.9" } }, "sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw=="],
|
||||
|
||||
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.0", "", {}, "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="],
|
||||
|
||||
"@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="],
|
||||
|
||||
"@types/ws": ["@types/ws@8.5.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw=="],
|
||||
|
||||
"@vue/compiler-core": ["@vue/compiler-core@3.5.13", "", { "dependencies": { "@babel/parser": "^7.25.3", "@vue/shared": "3.5.13", "entities": "^4.5.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.0" } }, "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q=="],
|
||||
|
||||
"@vue/compiler-dom": ["@vue/compiler-dom@3.5.13", "", { "dependencies": { "@vue/compiler-core": "3.5.13", "@vue/shared": "3.5.13" } }, "sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA=="],
|
||||
|
||||
"@vue/compiler-sfc": ["@vue/compiler-sfc@3.5.13", "", { "dependencies": { "@babel/parser": "^7.25.3", "@vue/compiler-core": "3.5.13", "@vue/compiler-dom": "3.5.13", "@vue/compiler-ssr": "3.5.13", "@vue/shared": "3.5.13", "estree-walker": "^2.0.2", "magic-string": "^0.30.11", "postcss": "^8.4.48", "source-map-js": "^1.2.0" } }, "sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ=="],
|
||||
|
||||
"@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.13", "", { "dependencies": { "@vue/compiler-dom": "3.5.13", "@vue/shared": "3.5.13" } }, "sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA=="],
|
||||
|
||||
"@vue/reactivity": ["@vue/reactivity@3.5.13", "", { "dependencies": { "@vue/shared": "3.5.13" } }, "sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg=="],
|
||||
|
||||
"@vue/runtime-core": ["@vue/runtime-core@3.5.13", "", { "dependencies": { "@vue/reactivity": "3.5.13", "@vue/shared": "3.5.13" } }, "sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw=="],
|
||||
|
||||
"@vue/runtime-dom": ["@vue/runtime-dom@3.5.13", "", { "dependencies": { "@vue/reactivity": "3.5.13", "@vue/runtime-core": "3.5.13", "@vue/shared": "3.5.13", "csstype": "^3.1.3" } }, "sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog=="],
|
||||
|
||||
"@vue/server-renderer": ["@vue/server-renderer@3.5.13", "", { "dependencies": { "@vue/compiler-ssr": "3.5.13", "@vue/shared": "3.5.13" }, "peerDependencies": { "vue": "3.5.13" } }, "sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA=="],
|
||||
|
||||
"@vue/shared": ["@vue/shared@3.5.13", "", {}, "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ=="],
|
||||
|
||||
"bun-types": ["bun-types@1.2.5-canary.20250306T140602", "", { "dependencies": { "@types/node": "*", "@types/ws": "~8.5.10" } }, "sha512-BQ4T9olKd4PBR4AHQXgwD5nU/Eurj9CqMKnCZkWoxztOmJBoS+XcLC7y+Wj589awl7HjnfE9GJqi6dLrMtVJmQ=="],
|
||||
|
||||
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
|
||||
|
||||
"entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
|
||||
|
||||
"estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
|
||||
|
||||
"magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="],
|
||||
|
||||
"nanoid": ["nanoid@3.3.8", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w=="],
|
||||
|
||||
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
||||
|
||||
"postcss": ["postcss@8.5.3", "", { "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A=="],
|
||||
|
||||
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
||||
|
||||
"typescript": ["typescript@5.8.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ=="],
|
||||
|
||||
"undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
|
||||
|
||||
"vue": ["vue@3.5.13", "", { "dependencies": { "@vue/compiler-dom": "3.5.13", "@vue/compiler-sfc": "3.5.13", "@vue/runtime-dom": "3.5.13", "@vue/server-renderer": "3.5.13", "@vue/shared": "3.5.13" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ=="],
|
||||
}
|
||||
}
|
||||
58
packages/bun-plugin-vue/compiled.js
Normal file
58
packages/bun-plugin-vue/compiled.js
Normal file
@@ -0,0 +1,58 @@
|
||||
/* Analyzed bindings: {
|
||||
"Hello": "setup-const",
|
||||
"ref": "setup-const",
|
||||
"msg": "setup-ref"
|
||||
} */
|
||||
import Hello from "./Hello.vue";
|
||||
import { ref } from "vue";
|
||||
|
||||
const __sfc__ = {
|
||||
__name: "App",
|
||||
setup(__props, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
const msg = ref("Hello World!");
|
||||
|
||||
const __returned__ = { msg, Hello, ref };
|
||||
Object.defineProperty(__returned__, "__isScriptSetup", { enumerable: false, value: true });
|
||||
return __returned__;
|
||||
},
|
||||
};
|
||||
import {
|
||||
createVNode as _createVNode,
|
||||
toDisplayString as _toDisplayString,
|
||||
createElementVNode as _createElementVNode,
|
||||
vModelText as _vModelText,
|
||||
withDirectives as _withDirectives,
|
||||
Fragment as _Fragment,
|
||||
openBlock as _openBlock,
|
||||
createElementBlock as _createElementBlock,
|
||||
} from "vue";
|
||||
function render(_ctx, _cache, $props, $setup, $data, $options) {
|
||||
return (
|
||||
_openBlock(),
|
||||
_createElementBlock(
|
||||
_Fragment,
|
||||
null,
|
||||
[
|
||||
_createVNode($setup["Hello"]),
|
||||
_createElementVNode("h1", null, _toDisplayString($setup.msg), 1 /* TEXT */),
|
||||
_withDirectives(
|
||||
_createElementVNode(
|
||||
"input",
|
||||
{
|
||||
"onUpdate:modelValue": _cache[0] || (_cache[0] = $event => ($setup.msg = $event)),
|
||||
},
|
||||
null,
|
||||
512 /* NEED_PATCH */,
|
||||
),
|
||||
[[_vModelText, $setup.msg]],
|
||||
),
|
||||
],
|
||||
64 /* STABLE_FRAGMENT */,
|
||||
)
|
||||
);
|
||||
}
|
||||
__sfc__.render = render;
|
||||
__sfc__.__file = "src/App.vue";
|
||||
export default __sfc__;
|
||||
35
packages/bun-plugin-vue/package.json
Normal file
35
packages/bun-plugin-vue/package.json
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "bun-plugin-vue",
|
||||
"version": "0.0.1",
|
||||
"description": "Official Vue plugin for Bun",
|
||||
"module": "src/index.ts",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "oxlint .",
|
||||
"check:types": "tsc --noEmit",
|
||||
"build:types": "tsc --emitDeclarationOnly --declaration --declarationDir ./dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vue/compiler-sfc": "^3.5.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5",
|
||||
"vue": "^3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"bun-types": "canary",
|
||||
"vue": "^3.5.13"
|
||||
},
|
||||
"files": [
|
||||
"README.md",
|
||||
"bunfig.toml",
|
||||
"tsconfig.json",
|
||||
"modules.d.ts",
|
||||
"dist",
|
||||
"src",
|
||||
"!src/**/*.spec.ts"
|
||||
]
|
||||
}
|
||||
29
packages/bun-plugin-vue/src/facade.spec.ts
Normal file
29
packages/bun-plugin-vue/src/facade.spec.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { test, expect } from "bun:test";
|
||||
import { VirtualModuleService } from "./facade";
|
||||
import { parse } from "@vue/compiler-sfc";
|
||||
|
||||
test("VirtualModuleService", () => {
|
||||
const App = /* vue */ `
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
msg: string
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="hello">
|
||||
Oh hi there, {{ msg }}!
|
||||
</main>
|
||||
</template>
|
||||
<style>
|
||||
.hello {
|
||||
color: red;
|
||||
}
|
||||
</style>
|
||||
`.trim();
|
||||
const service = new VirtualModuleService("/usr/bun")
|
||||
const component = parse(App, { filename: "/usr/bun/App.vue" });
|
||||
expect(component.errors).toBeEmpty();
|
||||
const actual = service.registerSFC(component.descriptor);
|
||||
|
||||
});
|
||||
76
packages/bun-plugin-vue/src/facade.ts
Normal file
76
packages/bun-plugin-vue/src/facade.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import path from "node:path";
|
||||
import { compileScript, compileTemplate, parse } from "@vue/compiler-sfc";
|
||||
import type { CompilerError, SFCDescriptor, SFCParseOptions, SFCScriptBlock, SFCStyleCompileResults, SFCTemplateBlock, SFCTemplateCompileResults } from "@vue/compiler-sfc";
|
||||
import assert from "node:assert";
|
||||
|
||||
/**
|
||||
* @see https://play.vuejs.org
|
||||
*/
|
||||
export class VirtualModuleService {
|
||||
private readonly root: string;
|
||||
private readonly ssr: boolean = false;
|
||||
private readonly sourceMap: boolean = true;
|
||||
|
||||
private scriptCache: Map<SFCDescriptor, SFCScriptBlock> = new Map();
|
||||
private templateCache: Map<SFCDescriptor, SFCTemplateCompileResults> = new Map();
|
||||
private styleCache: Map<SFCDescriptor, SFCStyleCompileResults> = new Map();
|
||||
|
||||
constructor(root: string) {
|
||||
this.root = root;
|
||||
}
|
||||
|
||||
public registerSFC(descriptor: SFCDescriptor): void {
|
||||
const { script, scriptSetup, slotted, styles, template } = descriptor
|
||||
// TODO: canInlineMain
|
||||
|
||||
const name = path.basename(descriptor.filename).split('.')[0];
|
||||
assert(name && typeof name === 'string');
|
||||
const relative = path.relative(this.root, descriptor.filename);
|
||||
|
||||
let facade = '';
|
||||
|
||||
if (script || scriptSetup) {
|
||||
const compiled = this.compileScript(descriptor);
|
||||
console.log(compiled)
|
||||
facade += /* js */ `import ${name} from "${relative}";\n`;
|
||||
}
|
||||
|
||||
if (template) {
|
||||
facade += compiled.code + '\n';
|
||||
}
|
||||
}
|
||||
|
||||
private compileTemplate(descriptor: SFCDescriptor) {
|
||||
const { template } = descriptor;
|
||||
assert(template);
|
||||
const cached = this.templateCache.get(descriptor);
|
||||
if (cached) return cached;
|
||||
const compiled = compileTemplate({
|
||||
id: 'bun',
|
||||
source: template.content,
|
||||
filename: descriptor.filename,
|
||||
compilerOptions: {
|
||||
ssr: this.ssr,
|
||||
scopeId: descriptor.styles.some(s => s.scoped) ? `data-v-${name}` : undefined,
|
||||
},
|
||||
});
|
||||
this.templateCache.set(descriptor, compiled);
|
||||
return compiled
|
||||
|
||||
}
|
||||
|
||||
|
||||
private compileScript(descriptor: SFCDescriptor): SFCScriptBlock {
|
||||
const cached = this.scriptCache.get(descriptor);
|
||||
if (cached) return cached;
|
||||
|
||||
const script = compileScript(descriptor, {
|
||||
id: 'bun', // todo: vite uses descriptor.id but that doesn't exist(?)
|
||||
sourceMap: this.sourceMap,
|
||||
isProd: false,
|
||||
});
|
||||
this.scriptCache.set(descriptor, script);
|
||||
return script;
|
||||
}
|
||||
|
||||
}
|
||||
67
packages/bun-plugin-vue/src/index.ts
Normal file
67
packages/bun-plugin-vue/src/index.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import type { BunPlugin, BuildConfig, OnLoadResult } from "bun";
|
||||
import { type CompilerError, parse } from "@vue/compiler-sfc";
|
||||
import { getBaseParseOptions } from "./options";
|
||||
|
||||
interface Options {}
|
||||
|
||||
function VuePlugin(option?: Options): BunPlugin {
|
||||
return {
|
||||
name: "bun-plugin-vue",
|
||||
setup(builder) {
|
||||
const parseOptions = getBaseParseOptions(builder.config);
|
||||
|
||||
builder.onLoad({ filter: /\.vue$/ }, async args => {
|
||||
const source = await Bun.file(args.path).text();
|
||||
require.resolve
|
||||
const { errors, descriptor } = parse(source, {
|
||||
...parseOptions,
|
||||
filename: args.path,
|
||||
|
||||
templateParseOptions: {
|
||||
// todo: onError, onWarn. Requires similar hooks in PluginBuilder
|
||||
},
|
||||
});
|
||||
|
||||
if (errors.length) throw new VueError(errors);
|
||||
console.log(descriptor);
|
||||
if (descriptor.script) {
|
||||
const compiled = new Bun.Transpiler().transform(descriptor.script!.src!);
|
||||
return {
|
||||
loader: "js",
|
||||
content: compiled
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
type VueParseError = CompilerError | SyntaxError;
|
||||
class VueError extends SyntaxError {
|
||||
public line: number | undefined;
|
||||
public column: number | undefined;
|
||||
public errors: VueParseError[];
|
||||
/** First error that occurred */
|
||||
public cause: VueParseError;
|
||||
|
||||
constructor(errors: VueParseError[]) {
|
||||
const [error] = errors;
|
||||
if (errors.length === 1) {
|
||||
const error = errors[0];
|
||||
super(error.message);
|
||||
this.cause = error;
|
||||
} else {
|
||||
super(`Vue compiler failed with ${errors.length} errors`);
|
||||
this.cause = error;
|
||||
}
|
||||
this.errors = errors;
|
||||
const start = (error as CompilerError).loc?.start;
|
||||
if (start) {
|
||||
this.line = start.line;
|
||||
this.column = start.column;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { VuePlugin, type Options };
|
||||
export default VuePlugin() as BunPlugin;
|
||||
5
packages/bun-plugin-vue/src/module.d.ts
vendored
Normal file
5
packages/bun-plugin-vue/src/module.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
declare module "*.vue" {
|
||||
import type { Component } from "vue";
|
||||
const component: Component;
|
||||
export default component;
|
||||
}
|
||||
32
packages/bun-plugin-vue/src/options.ts
Normal file
32
packages/bun-plugin-vue/src/options.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import type { SFCParseOptions } from "@vue/compiler-sfc";
|
||||
import type { BuildConfig } from "bun";
|
||||
|
||||
export function getBaseParseOptions(config: Partial<BuildConfig> | undefined): Partial<SFCParseOptions> {
|
||||
const { sourcemap, minify } = config ?? ({ __proto__: null } as Partial<BuildConfig>);
|
||||
const sourceMapEnabled = sourcemap && sourcemap !== "none";
|
||||
|
||||
return Object.assign(minifyOptions(minify), { sourceMap: sourceMapEnabled }) satisfies Partial<SFCParseOptions>;
|
||||
}
|
||||
|
||||
function minifyOptions(minify: BuildConfig["minify"]) {
|
||||
// preserve comments?
|
||||
var comments: boolean | undefined;
|
||||
var whitespace: "preserve" | "condense" | undefined;
|
||||
|
||||
switch (typeof minify) {
|
||||
case "boolean":
|
||||
comments = !minify;
|
||||
whitespace = minify ? "condense" : "preserve";
|
||||
break;
|
||||
case "object":
|
||||
comments = !minify.whitespace;
|
||||
whitespace = minify.whitespace ? "condense" : "preserve";
|
||||
break;
|
||||
case "undefined":
|
||||
break;
|
||||
default:
|
||||
console.warn(`[bun-plugin-vue] BuildConfig.minify must be a boolea, object, or undefined, got ${minify}`);
|
||||
}
|
||||
|
||||
return { comments, whitespace };
|
||||
}
|
||||
78
packages/bun-plugin-vue/test/fixtures/App.vue
vendored
Normal file
78
packages/bun-plugin-vue/test/fixtures/App.vue
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
<script setup lang="ts">
|
||||
import HelloWorld from './Hello.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<header>
|
||||
|
||||
<div class="wrapper">
|
||||
<HelloWorld msg="World" />
|
||||
|
||||
</div>
|
||||
</header>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
header {
|
||||
line-height: 1.5;
|
||||
max-height: 100vh;
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: block;
|
||||
margin: 0 auto 2rem;
|
||||
}
|
||||
|
||||
nav {
|
||||
width: 100%;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
nav a.router-link-exact-active {
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
nav a.router-link-exact-active:hover {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
nav a {
|
||||
display: inline-block;
|
||||
padding: 0 1rem;
|
||||
border-left: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
nav a:first-of-type {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
header {
|
||||
display: flex;
|
||||
place-items: center;
|
||||
padding-right: calc(var(--section-gap) / 2);
|
||||
}
|
||||
|
||||
.logo {
|
||||
margin: 0 2rem 0 0;
|
||||
}
|
||||
|
||||
header .wrapper {
|
||||
display: flex;
|
||||
place-items: flex-start;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
nav {
|
||||
text-align: left;
|
||||
margin-left: -1rem;
|
||||
font-size: 1rem;
|
||||
|
||||
padding: 1rem 0;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
11
packages/bun-plugin-vue/test/fixtures/Hello.vue
vendored
Normal file
11
packages/bun-plugin-vue/test/fixtures/Hello.vue
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
msg: string
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main>
|
||||
Oh hi there, {{ msg }}!
|
||||
</main>
|
||||
</template>
|
||||
31
packages/bun-plugin-vue/test/plugin.spec.ts
Normal file
31
packages/bun-plugin-vue/test/plugin.spec.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { describe, it, expect } from "bun:test";
|
||||
import path from "node:path";
|
||||
import pkg from "../package.json";
|
||||
import plugin, { VuePlugin } from "../src/index";
|
||||
|
||||
const fixture = (...segs: string[]) => path.resolve(import.meta.dir, "fixtures", ...segs);
|
||||
|
||||
describe("VuePlugin", () => {
|
||||
it("has the same name as the package", () => {
|
||||
expect(plugin.name).toBe(pkg.name);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Bundling Vue apps", () => {
|
||||
describe("Given a valid Vue app", () => {
|
||||
const appPath = fixture("App.vue");
|
||||
it("should bundle the app", async () => {
|
||||
const result = await Bun.build({
|
||||
entrypoints: [appPath],
|
||||
outdir: fixture("dist"),
|
||||
plugins: [VuePlugin()],
|
||||
});
|
||||
for (const log of result.logs) {
|
||||
console.log(log);
|
||||
}
|
||||
|
||||
expect(result.success).toBeTrue();
|
||||
expect(result.outputs.filter(out => ["js", "ts"].includes(out.loader))).not.toBeEmpty();
|
||||
});
|
||||
});
|
||||
});
|
||||
113
packages/bun-plugin-vue/tsconfig.json
Normal file
113
packages/bun-plugin-vue/tsconfig.json
Normal file
@@ -0,0 +1,113 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig to read more about this file */
|
||||
|
||||
/* Projects */
|
||||
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
|
||||
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
||||
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
|
||||
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
|
||||
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
||||
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||
|
||||
/* Language and Environment */
|
||||
"target": "esnext", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||
"jsx": "preserve", /* Specify what JSX code is generated. */
|
||||
// "libReplacement": true, /* Enable lib replacement. */
|
||||
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
|
||||
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
|
||||
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
|
||||
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||
"moduleDetection": "force", /* Control what method is used to detect module-format JS files. */
|
||||
|
||||
/* Modules */
|
||||
"module": "ESNext", /* Specify what module code is generated. */
|
||||
"rootDir": "./src", /* Specify the root folder within your source files. */
|
||||
"moduleResolution": "bundler", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
|
||||
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
||||
// "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
|
||||
// "rewriteRelativeImportExtensions": true, /* Rewrite '.ts', '.tsx', '.mts', and '.cts' file extensions in relative import paths to their JavaScript equivalent in output files. */
|
||||
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
|
||||
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
|
||||
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
|
||||
// "noUncheckedSideEffectImports": true, /* Check side effect imports. */
|
||||
"resolveJsonModule": true, /* Enable importing .json files. */
|
||||
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
|
||||
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
||||
|
||||
/* JavaScript Support */
|
||||
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
|
||||
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
||||
|
||||
/* Emit */
|
||||
"declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||
"declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||
"emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||
// "noEmit": true, /* Disable emitting files from a compilation. */
|
||||
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
||||
// "outDir": "./", /* Specify an output folder for all emitted files. */
|
||||
// "removeComments": true, /* Disable emitting comments. */
|
||||
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
||||
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
||||
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
||||
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
||||
"stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
|
||||
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
|
||||
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
||||
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
|
||||
"declarationDir": "./dist", /* Specify the output directory for generated declaration files. */
|
||||
|
||||
/* Interop Constraints */
|
||||
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||
// "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
|
||||
"isolatedDeclarations": true, /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */
|
||||
// "erasableSyntaxOnly": true, /* Do not allow runtime constructs that are not part of ECMAScript. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
|
||||
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
||||
|
||||
/* Type Checking */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
"noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
||||
"strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
|
||||
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
||||
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
|
||||
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
||||
// "strictBuiltinIteratorReturn": true, /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */
|
||||
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
||||
"useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
||||
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
||||
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
||||
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
|
||||
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
||||
"noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
||||
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
|
||||
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
||||
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
|
||||
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
||||
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
||||
|
||||
/* Completeness */
|
||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||
}
|
||||
}
|
||||
28
packages/bun-plugin-vue/tsconfig.json.bak
Normal file
28
packages/bun-plugin-vue/tsconfig.json.bak
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
// Environment setup & latest features
|
||||
"lib": ["esnext"],
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleDetection": "force",
|
||||
"jsx": "react-jsx",
|
||||
"allowJs": true,
|
||||
|
||||
// Bundler mode
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"noEmit": true,
|
||||
|
||||
// Best practices
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
|
||||
// Some stricter flags (disabled by default)
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"noPropertyAccessFromIndexSignature": false
|
||||
}
|
||||
}
|
||||
@@ -739,7 +739,7 @@ pub const JSBundler = struct {
|
||||
.value = .pending,
|
||||
.path = parse.path.text,
|
||||
.namespace = parse.path.namespace,
|
||||
.was_file = false,
|
||||
.was_file = parse.path.isFile(),
|
||||
.called_defer = false,
|
||||
.task = undefined,
|
||||
.js_task = undefined,
|
||||
@@ -1013,7 +1013,7 @@ pub const JSBundler = struct {
|
||||
onstart_promises_array: JSC.JSValue,
|
||||
is_last: bool,
|
||||
is_bake: bool,
|
||||
) !JSValue {
|
||||
) bun.JSError!JSValue {
|
||||
JSC.markBinding(@src());
|
||||
const tracer = bun.tracy.traceNamed(@src(), "JSBundler.addPlugin");
|
||||
defer tracer.end();
|
||||
|
||||
@@ -2207,7 +2207,8 @@ pub const BundleV2 = struct {
|
||||
const source = &this.graph.input_files.items(.source)[load.source_index.get()];
|
||||
// If it's a file namespace, we should run it through the parser like normal.
|
||||
// The file could be on disk.
|
||||
if (source.path.isFile()) {
|
||||
if (load.was_file) {
|
||||
bun.assert(source.path.isFile());
|
||||
this.graph.pool.schedule(load.parse_task);
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user