mirror of
https://github.com/oven-sh/bun
synced 2026-02-15 13:22:07 +00:00
528 lines
19 KiB
TypeScript
528 lines
19 KiB
TypeScript
import { itBundled } from "../expectBundled";
|
|
import { describe, expect } from "bun:test";
|
|
|
|
// Tests ported from:
|
|
// https://github.com/evanw/esbuild/blob/main/internal/bundler_tests/bundler_css_test.go
|
|
|
|
// For debug, all files are written to $TEMP/bun-bundle-tests/css
|
|
|
|
describe.todo("bundler", () => {
|
|
itBundled("css/CSSEntryPoint", {
|
|
// GENERATED
|
|
files: {
|
|
"/entry.css": /* css */ `
|
|
body {
|
|
background: white;
|
|
color: black }
|
|
`,
|
|
},
|
|
});
|
|
itBundled("css/CSSAtImportMissing", {
|
|
files: {
|
|
"/entry.css": `@import "./missing.css";`,
|
|
},
|
|
bundleErrors: {
|
|
"/entry.css": ['Could not resolve "./missing.css"'],
|
|
},
|
|
});
|
|
itBundled("css/CSSAtImportExternal", {
|
|
// GENERATED
|
|
files: {
|
|
"/entry.css": /* css */ `
|
|
@import "./internal.css";
|
|
@import "./external1.css";
|
|
@import "./external2.css";
|
|
@import "./charset1.css";
|
|
@import "./charset2.css";
|
|
@import "./external5.css" screen;
|
|
`,
|
|
"/internal.css": /* css */ `
|
|
@import "./external5.css" print;
|
|
.before { color: red }
|
|
`,
|
|
"/charset1.css": /* css */ `
|
|
@charset "UTF-8";
|
|
@import "./external3.css";
|
|
@import "./external4.css";
|
|
@import "./external5.css";
|
|
@import "https://www.example.com/style1.css";
|
|
@import "https://www.example.com/style2.css";
|
|
@import "https://www.example.com/style3.css" print;
|
|
.middle { color: green }
|
|
`,
|
|
"/charset2.css": /* css */ `
|
|
@charset "UTF-8";
|
|
@import "./external3.css";
|
|
@import "./external5.css" screen;
|
|
@import "https://www.example.com/style1.css";
|
|
@import "https://www.example.com/style3.css";
|
|
.after { color: blue }
|
|
`,
|
|
},
|
|
});
|
|
itBundled("css/CSSAtImport", {
|
|
// GENERATED
|
|
files: {
|
|
"/entry.css": /* css */ `
|
|
@import "./a.css";
|
|
@import "./b.css";
|
|
.entry { color: red }
|
|
`,
|
|
"/a.css": /* css */ `
|
|
@import "./shared.css";
|
|
.a { color: green }
|
|
`,
|
|
"/b.css": /* css */ `
|
|
@import "./shared.css";
|
|
.b { color: blue }
|
|
`,
|
|
"/shared.css": `.shared { color: black }`,
|
|
},
|
|
});
|
|
itBundled("css/CSSFromJSMissingImport", {
|
|
// GENERATED
|
|
files: {
|
|
"/entry.js": /* js */ `
|
|
import {missing} from "./a.css"
|
|
console.log(missing)
|
|
`,
|
|
"/a.css": `.a { color: red }`,
|
|
},
|
|
/* TODO FIX expectedCompileLog: `entry.js: ERROR: No matching export in "a.css" for import "missing"
|
|
`, */
|
|
});
|
|
itBundled("css/CSSFromJSMissingStarImport", {
|
|
// GENERATED
|
|
files: {
|
|
"/entry.js": /* js */ `
|
|
import * as ns from "./a.css"
|
|
console.log(ns.missing)
|
|
`,
|
|
"/a.css": `.a { color: red }`,
|
|
},
|
|
});
|
|
itBundled("css/ImportCSSFromJS", {
|
|
// GENERATED
|
|
files: {
|
|
"/entry.js": /* js */ `
|
|
import "./a.js"
|
|
import "./b.js"
|
|
`,
|
|
"/a.js": /* js */ `
|
|
import "./a.css";
|
|
console.log('a')
|
|
`,
|
|
"/a.css": `.a { color: red }`,
|
|
"/b.js": /* js */ `
|
|
import "./b.css";
|
|
console.log('b')
|
|
`,
|
|
"/b.css": `.b { color: blue }`,
|
|
},
|
|
});
|
|
itBundled("css/ImportCSSFromJSWriteToStdout", {
|
|
// GENERATED
|
|
files: {
|
|
"/entry.js": `import "./entry.css"`,
|
|
"/entry.css": `.entry { color: red }`,
|
|
},
|
|
/* TODO FIX expectedScanLog: `entry.js: ERROR: Cannot import "entry.css" into a JavaScript file without an output path configured
|
|
`, */
|
|
});
|
|
itBundled("css/ImportJSFromCSS", {
|
|
// GENERATED
|
|
files: {
|
|
"/entry.js": `export default 123`,
|
|
"/entry.css": `@import "./entry.js";`,
|
|
},
|
|
entryPoints: ["/entry.css"],
|
|
/* TODO FIX expectedScanLog: `entry.css: ERROR: Cannot import "entry.js" into a CSS file
|
|
NOTE: An "@import" rule can only be used to import another CSS file, and "entry.js" is not a CSS file (it was loaded with the "js" loader).
|
|
`, */
|
|
});
|
|
itBundled("css/ImportJSONFromCSS", {
|
|
// GENERATED
|
|
files: {
|
|
"/entry.json": `{}`,
|
|
"/entry.css": `@import "./entry.json";`,
|
|
},
|
|
entryPoints: ["/entry.css"],
|
|
/* TODO FIX expectedScanLog: `entry.css: ERROR: Cannot import "entry.json" into a CSS file
|
|
NOTE: An "@import" rule can only be used to import another CSS file, and "entry.json" is not a CSS file (it was loaded with the "json" loader).
|
|
`, */
|
|
});
|
|
itBundled("css/MissingImportURLInCSS", {
|
|
// GENERATED
|
|
files: {
|
|
"/src/entry.css": /* css */ `
|
|
a { background: url(./one.png); }
|
|
b { background: url("./two.png"); }
|
|
`,
|
|
},
|
|
/* TODO FIX expectedScanLog: `src/entry.css: ERROR: Could not resolve "./one.png"
|
|
src/entry.css: ERROR: Could not resolve "./two.png"
|
|
`, */
|
|
});
|
|
itBundled("css/ExternalImportURLInCSS", {
|
|
// GENERATED
|
|
files: {
|
|
"/src/entry.css": /* css */ `
|
|
div:after {
|
|
content: 'If this is recognized, the path should become "../src/external.png"';
|
|
background: url(./external.png);
|
|
}
|
|
|
|
/* These URLs should be external automatically */
|
|
a { background: url(http://example.com/images/image.png) }
|
|
b { background: url(https://example.com/images/image.png) }
|
|
c { background: url(//example.com/images/image.png) }
|
|
d { background: url(data:image/png;base64,iVBORw0KGgo=) }
|
|
path { fill: url(#filter) }
|
|
`,
|
|
},
|
|
});
|
|
itBundled("css/InvalidImportURLInCSS", {
|
|
// GENERATED
|
|
files: {
|
|
"/entry.css": /* css */ `
|
|
a {
|
|
background: url(./js.js);
|
|
background: url("./jsx.jsx");
|
|
background: url(./ts.ts);
|
|
background: url('./tsx.tsx');
|
|
background: url(./json.json);
|
|
background: url(./css.css);
|
|
}
|
|
`,
|
|
"/js.js": `export default 123`,
|
|
"/jsx.jsx": `export default 123`,
|
|
"/ts.ts": `export default 123`,
|
|
"/tsx.tsx": `export default 123`,
|
|
"/json.json": `{ "test": true }`,
|
|
"/css.css": `a { color: red }`,
|
|
},
|
|
/* TODO FIX expectedScanLog: `entry.css: ERROR: Cannot use "js.js" as a URL
|
|
NOTE: You can't use a "url()" token to reference the file "js.js" because it was loaded with the "js" loader, which doesn't provide a URL to embed in the resulting CSS.
|
|
entry.css: ERROR: Cannot use "jsx.jsx" as a URL
|
|
NOTE: You can't use a "url()" token to reference the file "jsx.jsx" because it was loaded with the "jsx" loader, which doesn't provide a URL to embed in the resulting CSS.
|
|
entry.css: ERROR: Cannot use "ts.ts" as a URL
|
|
NOTE: You can't use a "url()" token to reference the file "ts.ts" because it was loaded with the "ts" loader, which doesn't provide a URL to embed in the resulting CSS.
|
|
entry.css: ERROR: Cannot use "tsx.tsx" as a URL
|
|
NOTE: You can't use a "url()" token to reference the file "tsx.tsx" because it was loaded with the "tsx" loader, which doesn't provide a URL to embed in the resulting CSS.
|
|
entry.css: ERROR: Cannot use "json.json" as a URL
|
|
NOTE: You can't use a "url()" token to reference the file "json.json" because it was loaded with the "json" loader, which doesn't provide a URL to embed in the resulting CSS.
|
|
entry.css: ERROR: Cannot use "css.css" as a URL
|
|
NOTE: You can't use a "url()" token to reference a CSS file, and "css.css" is a CSS file (it was loaded with the "css" loader).
|
|
`, */
|
|
});
|
|
itBundled("css/TextImportURLInCSSText", {
|
|
// GENERATED
|
|
files: {
|
|
"/entry.css": /* css */ `
|
|
a {
|
|
background: url(./example.txt);
|
|
}
|
|
`,
|
|
"/example.txt": `This is some text.`,
|
|
},
|
|
});
|
|
itBundled("css/DataURLImportURLInCSS", {
|
|
// GENERATED
|
|
files: {
|
|
"/entry.css": /* css */ `
|
|
a {
|
|
background: url(./example.png);
|
|
}
|
|
`,
|
|
"/example.png": `\x89\x50\x4E\x47\x0D\x0A\x1A\x0A`,
|
|
},
|
|
});
|
|
itBundled("css/BinaryImportURLInCSS", {
|
|
// GENERATED
|
|
files: {
|
|
"/entry.css": /* css */ `
|
|
a {
|
|
background: url(./example.png);
|
|
}
|
|
`,
|
|
"/example.png": `\x89\x50\x4E\x47\x0D\x0A\x1A\x0A`,
|
|
},
|
|
});
|
|
itBundled("css/Base64ImportURLInCSS", {
|
|
// GENERATED
|
|
files: {
|
|
"/entry.css": /* css */ `
|
|
a {
|
|
background: url(./example.png);
|
|
}
|
|
`,
|
|
"/example.png": `\x89\x50\x4E\x47\x0D\x0A\x1A\x0A`,
|
|
},
|
|
});
|
|
itBundled("css/FileImportURLInCSS", {
|
|
// GENERATED
|
|
files: {
|
|
"/entry.css": /* css */ `
|
|
@import "./one.css";
|
|
@import "./two.css";
|
|
`,
|
|
"/one.css": `a { background: url(./example.data) }`,
|
|
"/two.css": `b { background: url(./example.data) }`,
|
|
"/example.data": `This is some data.`,
|
|
},
|
|
});
|
|
itBundled("css/IgnoreURLsInAtRulePrelude", {
|
|
// GENERATED
|
|
files: {
|
|
"/entry.css": /* css */ `
|
|
/* This should not generate a path resolution error */
|
|
@supports (background: url(ignored.png)) {
|
|
a { color: red }
|
|
}
|
|
`,
|
|
},
|
|
});
|
|
itBundled("css/PackageURLsInCSS", {
|
|
// GENERATED
|
|
files: {
|
|
"/entry.css": /* css */ `
|
|
@import "test.css";
|
|
|
|
a { background: url(a/1.png); }
|
|
b { background: url(b/2.png); }
|
|
c { background: url(c/3.png); }
|
|
`,
|
|
"/test.css": `.css { color: red }`,
|
|
"/a/1.png": `a-1`,
|
|
"/node_modules/b/2.png": `b-2-node_modules`,
|
|
"/c/3.png": `c-3`,
|
|
"/node_modules/c/3.png": `c-3-node_modules`,
|
|
},
|
|
});
|
|
itBundled("css/CSSAtImportExtensionOrderCollision", {
|
|
// GENERATED
|
|
files: {
|
|
"/entry.css": `@import "./test";`,
|
|
"/test.js": `console.log('js')`,
|
|
"/test.css": `.css { color: red }`,
|
|
},
|
|
outfile: "/out.css",
|
|
extensionOrder: [".js", ".css"],
|
|
});
|
|
itBundled("css/CSSAtImportExtensionOrderCollisionUnsupported", {
|
|
// GENERATED
|
|
files: {
|
|
"/entry.css": `@import "./test";`,
|
|
"/test.js": `console.log('js')`,
|
|
"/test.sass": `// some code`,
|
|
},
|
|
outfile: "/out.css",
|
|
extensionOrder: [".js", ".sass"],
|
|
bundleErrors: {
|
|
"/entry.css": ['ERROR: No loader is configured for ".sass" files: test.sass'],
|
|
},
|
|
});
|
|
itBundled("css/CSSAtImportConditionsNoBundle", {
|
|
// GENERATED
|
|
files: {
|
|
"/entry.css": `@import "./print.css" print;`,
|
|
},
|
|
mode: "passthrough",
|
|
});
|
|
itBundled("css/CSSAtImportConditionsBundleExternal", {
|
|
// GENERATED
|
|
files: {
|
|
"/entry.css": `@import "https://example.com/print.css" print;`,
|
|
},
|
|
});
|
|
itBundled("css/CSSAtImportConditionsBundleExternalConditionWithURL", {
|
|
// GENERATED
|
|
files: {
|
|
"/entry.css": `@import "https://example.com/foo.css" (foo: url("foo.png")) and (bar: url("bar.png"));`,
|
|
},
|
|
});
|
|
itBundled("css/CSSAtImportConditionsBundle", {
|
|
// GENERATED
|
|
files: {
|
|
"/entry.css": `@import "./print.css" print;`,
|
|
"/print.css": `body { color: red }`,
|
|
},
|
|
/* TODO FIX expectedScanLog: `entry.css: ERROR: Bundling with conditional "@import" rules is not currently supported
|
|
`, */
|
|
});
|
|
itBundled("css/CSSAndJavaScriptCodeSplittingESBuildIssue1064", {
|
|
// GENERATED
|
|
files: {
|
|
"/a.js": /* js */ `
|
|
import shared from './shared.js'
|
|
console.log(shared() + 1)
|
|
`,
|
|
"/b.js": /* js */ `
|
|
import shared from './shared.js'
|
|
console.log(shared() + 2)
|
|
`,
|
|
"/c.css": /* css */ `
|
|
@import "./shared.css";
|
|
body { color: red }
|
|
`,
|
|
"/d.css": /* css */ `
|
|
@import "./shared.css";
|
|
body { color: blue }
|
|
`,
|
|
"/shared.js": `export default function() { return 3 }`,
|
|
"/shared.css": `body { background: black }`,
|
|
},
|
|
entryPoints: ["/a.js", "/b.js", "/c.css", "/d.css"],
|
|
format: "esm",
|
|
splitting: true,
|
|
});
|
|
itBundled("css/CSSExternalQueryAndHashNoMatchESBuildIssue1822", {
|
|
// GENERATED
|
|
files: {
|
|
"/entry.css": /* css */ `
|
|
a { background: url(foo/bar.png?baz) }
|
|
b { background: url(foo/bar.png#baz) }
|
|
`,
|
|
},
|
|
outfile: "/out.css",
|
|
/* TODO FIX expectedScanLog: `entry.css: ERROR: Could not resolve "foo/bar.png?baz"
|
|
NOTE: You can mark the path "foo/bar.png?baz" as external to exclude it from the bundle, which will remove this error.
|
|
entry.css: ERROR: Could not resolve "foo/bar.png#baz"
|
|
NOTE: You can mark the path "foo/bar.png#baz" as external to exclude it from the bundle, which will remove this error.
|
|
`, */
|
|
});
|
|
itBundled("css/CSSExternalQueryAndHashMatchESBuildIssue1822", {
|
|
// GENERATED
|
|
files: {
|
|
"/entry.css": /* css */ `
|
|
a { background: url(foo/bar.png?baz) }
|
|
b { background: url(foo/bar.png#baz) }
|
|
`,
|
|
},
|
|
outfile: "/out.css",
|
|
});
|
|
itBundled("css/CSSNestingOldBrowser", {
|
|
// GENERATED
|
|
files: {
|
|
"/nested-@layer.css": `a { @layer base { color: red; } }`,
|
|
"/nested-@media.css": `a { @media screen { color: red; } }`,
|
|
"/nested-ampersand-twice.css": `a { &, & { color: red; } }`,
|
|
"/nested-ampersand-first.css": `a { &, b { color: red; } }`,
|
|
"/nested-attribute.css": `a { [href] { color: red; } }`,
|
|
"/nested-colon.css": `a { :hover { color: red; } }`,
|
|
"/nested-dot.css": `a { .cls { color: red; } }`,
|
|
"/nested-greaterthan.css": `a { > b { color: red; } }`,
|
|
"/nested-hash.css": `a { #id { color: red; } }`,
|
|
"/nested-plus.css": `a { + b { color: red; } }`,
|
|
"/nested-tilde.css": `a { ~ b { color: red; } }`,
|
|
"/toplevel-ampersand-twice.css": `&, & { color: red; }`,
|
|
"/toplevel-ampersand-first.css": `&, a { color: red; }`,
|
|
"/toplevel-ampersand-second.css": `a, & { color: red; }`,
|
|
"/toplevel-attribute.css": `[href] { color: red; }`,
|
|
"/toplevel-colon.css": `:hover { color: red; }`,
|
|
"/toplevel-dot.css": `.cls { color: red; }`,
|
|
"/toplevel-greaterthan.css": `> b { color: red; }`,
|
|
"/toplevel-hash.css": `#id { color: red; }`,
|
|
"/toplevel-plus.css": `+ b { color: red; }`,
|
|
"/toplevel-tilde.css": `~ b { color: red; }`,
|
|
},
|
|
entryPoints: [
|
|
"/nested-@layer.css",
|
|
"/nested-@media.css",
|
|
"/nested-ampersand-twice.css",
|
|
"/nested-ampersand-first.css",
|
|
"/nested-attribute.css",
|
|
"/nested-colon.css",
|
|
"/nested-dot.css",
|
|
"/nested-greaterthan.css",
|
|
"/nested-hash.css",
|
|
"/nested-plus.css",
|
|
"/nested-tilde.css",
|
|
"/toplevel-ampersand-twice.css",
|
|
"/toplevel-ampersand-first.css",
|
|
"/toplevel-ampersand-second.css",
|
|
"/toplevel-attribute.css",
|
|
"/toplevel-colon.css",
|
|
"/toplevel-dot.css",
|
|
"/toplevel-greaterthan.css",
|
|
"/toplevel-hash.css",
|
|
"/toplevel-plus.css",
|
|
"/toplevel-tilde.css",
|
|
],
|
|
unsupportedCSSFeatures: "Nesting",
|
|
/* TODO FIX expectedScanLog: `nested-@layer.css: WARNING: CSS nesting syntax is not supported in the configured target environment (chrome10)
|
|
nested-@media.css: WARNING: CSS nesting syntax is not supported in the configured target environment (chrome10)
|
|
nested-ampersand-first.css: WARNING: CSS nesting syntax is not supported in the configured target environment (chrome10)
|
|
nested-ampersand-twice.css: WARNING: CSS nesting syntax is not supported in the configured target environment (chrome10)
|
|
nested-attribute.css: WARNING: CSS nesting syntax is not supported in the configured target environment (chrome10)
|
|
nested-colon.css: WARNING: CSS nesting syntax is not supported in the configured target environment (chrome10)
|
|
nested-dot.css: WARNING: CSS nesting syntax is not supported in the configured target environment (chrome10)
|
|
nested-greaterthan.css: WARNING: CSS nesting syntax is not supported in the configured target environment (chrome10)
|
|
nested-hash.css: WARNING: CSS nesting syntax is not supported in the configured target environment (chrome10)
|
|
nested-plus.css: WARNING: CSS nesting syntax is not supported in the configured target environment (chrome10)
|
|
nested-tilde.css: WARNING: CSS nesting syntax is not supported in the configured target environment (chrome10)
|
|
toplevel-ampersand-first.css: WARNING: CSS nesting syntax is not supported in the configured target environment (chrome10)
|
|
toplevel-ampersand-second.css: WARNING: CSS nesting syntax is not supported in the configured target environment (chrome10)
|
|
toplevel-ampersand-twice.css: WARNING: CSS nesting syntax is not supported in the configured target environment (chrome10)
|
|
toplevel-ampersand-twice.css: WARNING: CSS nesting syntax is not supported in the configured target environment (chrome10)
|
|
toplevel-greaterthan.css: WARNING: CSS nesting syntax is not supported in the configured target environment (chrome10)
|
|
toplevel-plus.css: WARNING: CSS nesting syntax is not supported in the configured target environment (chrome10)
|
|
toplevel-tilde.css: WARNING: CSS nesting syntax is not supported in the configured target environment (chrome10)
|
|
`, */
|
|
});
|
|
itBundled("css/MetafileCSSBundleTwoToOne", {
|
|
files: {
|
|
"/foo/entry.js": /* js */ `
|
|
import '../common.css'
|
|
console.log('foo')
|
|
`,
|
|
"/bar/entry.js": /* js */ `
|
|
import '../common.css'
|
|
console.log('bar')
|
|
`,
|
|
"/common.css": `body { color: red }`,
|
|
},
|
|
metafile: true,
|
|
entryPoints: ["/foo/entry.js", "/bar/entry.js"],
|
|
entryNaming: "[ext]/[hash]",
|
|
outdir: "/",
|
|
});
|
|
itBundled("css/DeduplicateRules", {
|
|
// GENERATED
|
|
files: {
|
|
"/yes0.css": `a { color: red; color: green; color: red }`,
|
|
"/yes1.css": `a { color: red } a { color: green } a { color: red }`,
|
|
"/yes2.css": `@media screen { a { color: red } } @media screen { a { color: red } }`,
|
|
"/no0.css": `@media screen { a { color: red } } @media screen { & a { color: red } }`,
|
|
"/no1.css": `@media screen { a { color: red } } @media screen { a[x] { color: red } }`,
|
|
"/no2.css": `@media screen { a { color: red } } @media screen { a.x { color: red } }`,
|
|
"/no3.css": `@media screen { a { color: red } } @media screen { a#x { color: red } }`,
|
|
"/no4.css": `@media screen { a { color: red } } @media screen { a:x { color: red } }`,
|
|
"/no5.css": `@media screen { a:x { color: red } } @media screen { a:x(y) { color: red } }`,
|
|
"/no6.css": `@media screen { a b { color: red } } @media screen { a + b { color: red } }`,
|
|
"/across-files.css": `@import 'across-files-0.css'; @import 'across-files-1.css'; @import 'across-files-2.css';`,
|
|
"/across-files-0.css": `a { color: red; color: red }`,
|
|
"/across-files-1.css": `a { color: green }`,
|
|
"/across-files-2.css": `a { color: red }`,
|
|
"/across-files-url.css": `@import 'across-files-url-0.css'; @import 'across-files-url-1.css'; @import 'across-files-url-2.css';`,
|
|
"/across-files-url-0.css": `@import 'http://example.com/some.css'; @font-face { src: url(http://example.com/some.font); }`,
|
|
"/across-files-url-1.css": `@font-face { src: url(http://example.com/some.other.font); }`,
|
|
"/across-files-url-2.css": `@font-face { src: url(http://example.com/some.font); }`,
|
|
},
|
|
entryPoints: [
|
|
"/yes0.css",
|
|
"/yes1.css",
|
|
"/yes2.css",
|
|
"/no0.css",
|
|
"/no1.css",
|
|
"/no2.css",
|
|
"/no3.css",
|
|
"/no4.css",
|
|
"/no5.css",
|
|
"/no6.css",
|
|
"/across-files.css",
|
|
"/across-files-url.css",
|
|
],
|
|
});
|
|
});
|