@kirana-style/rolldown 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -2
- package/package.json +47 -4
- package/src/index.d.ts +102 -0
- package/src/index.js +184 -0
package/README.md
CHANGED
|
@@ -1,3 +1,25 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @kirana-style/rolldown
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The Rolldown adapter for Kirana ([11-integrations](../../../../spec/11-integrations.md)).
|
|
4
|
+
|
|
5
|
+
```js
|
|
6
|
+
// rolldown.config.mjs
|
|
7
|
+
import kirana from "@kirana-style/rolldown"
|
|
8
|
+
import { defineConfig } from "rolldown"
|
|
9
|
+
|
|
10
|
+
export default defineConfig({
|
|
11
|
+
input: "src/main.tsx",
|
|
12
|
+
output: { dir: "dist" },
|
|
13
|
+
plugins: [kirana()],
|
|
14
|
+
})
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
- **Transform.** Modules that mention `@kirana-style/` or an `sx` attribute are compiled; errors fail the
|
|
18
|
+
build with the source line, warnings are printed. A watch rebuild re-runs the importers of changed
|
|
19
|
+
tokens and relations.
|
|
20
|
+
- **Stylesheet.** Rolldown does not bundle CSS, so the plugin emits the finished stylesheet as
|
|
21
|
+
`kirana.css` (`kirana({ fileName })` to change it) and importing `@kirana-style/core/stylesheet.css`
|
|
22
|
+
yields that name. Link it from your HTML.
|
|
23
|
+
|
|
24
|
+
Options: `root` (default: the working directory), `config` (overrides `kirana.config.*` key by key),
|
|
25
|
+
`reportInfo`, `fileName`. `@kirana-style/vite` is this plugin plus Vite's dev server.
|
package/package.json
CHANGED
|
@@ -1,6 +1,49 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kirana-style/rolldown",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Kirana for Rolldown: compiles modules and emits the stylesheet.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "The Kirana authors",
|
|
7
|
+
"homepage": "https://kirana.style",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/kirana-style/kirana.git",
|
|
11
|
+
"directory": "packages/kirana/npm/rolldown"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/kirana-style/kirana/issues"
|
|
15
|
+
},
|
|
16
|
+
"type": "module",
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./src/index.d.ts",
|
|
20
|
+
"default": "./src/index.js"
|
|
21
|
+
},
|
|
22
|
+
"./package.json": "./package.json"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@kirana-style/compiler": "0.1.0",
|
|
26
|
+
"@kirana-style/core": "0.1.0"
|
|
27
|
+
},
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"rolldown": "^1.2.12"
|
|
30
|
+
},
|
|
31
|
+
"peerDependenciesMeta": {
|
|
32
|
+
"rolldown": {
|
|
33
|
+
"optional": true
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"files": [
|
|
37
|
+
"src",
|
|
38
|
+
"bin",
|
|
39
|
+
"*.d.ts",
|
|
40
|
+
"*.css",
|
|
41
|
+
"README.md",
|
|
42
|
+
"builders.json",
|
|
43
|
+
"schemas",
|
|
44
|
+
"gatsby-node.mjs",
|
|
45
|
+
"rules",
|
|
46
|
+
"recommended.json",
|
|
47
|
+
"strict.json"
|
|
48
|
+
]
|
|
49
|
+
}
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Rolldown host adapter (KRN-INT-001, KRN-INT-002). It selects modules, forwards transform
|
|
3
|
+
* results, emits the stylesheet entry and forwards file changes to the Driver; it never interprets
|
|
4
|
+
* styles (D-028).
|
|
5
|
+
*
|
|
6
|
+
* The Vite adapter is this plugin plus Vite's dev-server hooks, because Vite's plugin API extends
|
|
7
|
+
* Rolldown's: one implementation of the build path serves both hosts. The one difference is CSS:
|
|
8
|
+
* Vite bundles it, so the stylesheet entry is a placeholder Vite processes and the plugin fills in;
|
|
9
|
+
* Rolldown alone does not, so the plugin emits the stylesheet as an asset of its own.
|
|
10
|
+
*/
|
|
11
|
+
import { Driver } from "@kirana-style/compiler";
|
|
12
|
+
import type { DriverOptions } from "@kirana-style/compiler";
|
|
13
|
+
import type { KiranaConfig } from "@kirana-style/core/config";
|
|
14
|
+
import type { OutputBundle, Plugin } from "rolldown";
|
|
15
|
+
export type KiranaRolldownOptions = {
|
|
16
|
+
/** The project root: where `kirana.config.*` and `@kirana-style/core` are found. Default: the working directory. */
|
|
17
|
+
root?: string;
|
|
18
|
+
/** Options that override `kirana.config.*`. */
|
|
19
|
+
config?: KiranaConfig;
|
|
20
|
+
/** Report `info` diagnostics (such as `dynamic-bridged`) as warnings. */
|
|
21
|
+
reportInfo?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Where the stylesheet is written in the output, when Rolldown builds on its own. Rolldown does
|
|
24
|
+
* not bundle CSS, so importing `@kirana-style/core/stylesheet.css` yields this name and the finished
|
|
25
|
+
* stylesheet is emitted under it; link it from your HTML. Default `kirana.css`. Under Vite, which
|
|
26
|
+
* bundles CSS, the stylesheet follows Vite's asset naming instead.
|
|
27
|
+
*/
|
|
28
|
+
fileName?: string;
|
|
29
|
+
/** Injected Driver options (tests). */
|
|
30
|
+
driver?: Pick<DriverOptions, "native" | "importModule">;
|
|
31
|
+
};
|
|
32
|
+
/** What a host that extends this plugin supplies. */
|
|
33
|
+
export type Host = {
|
|
34
|
+
/** True while a dev server serves the live stylesheet instead of the placeholder. */
|
|
35
|
+
development?: () => boolean;
|
|
36
|
+
/** A transform changed the stylesheet. */
|
|
37
|
+
cssChanged?: () => void;
|
|
38
|
+
/** The host bundles CSS (Vite): the entry is a placeholder stylesheet it processes like any other. */
|
|
39
|
+
bundlesCss?: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* False when something later renames the finished stylesheet itself (`@kirana-style/astro`, because
|
|
42
|
+
* Astro links each page's CSS by the name it recorded before `generateBundle`): the asset is filled
|
|
43
|
+
* where it is, and its name is added to `filled`.
|
|
44
|
+
*/
|
|
45
|
+
renames?: boolean;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* The plugin, with the session and the settings a host that extends it may change (the Vite
|
|
49
|
+
* adapter sets the root from Vite's resolved configuration).
|
|
50
|
+
*/
|
|
51
|
+
export declare function createKirana(options?: KiranaRolldownOptions, host?: Host): {
|
|
52
|
+
plugin: {
|
|
53
|
+
name: string;
|
|
54
|
+
buildStart(): Promise<void>;
|
|
55
|
+
load: {
|
|
56
|
+
filter: {
|
|
57
|
+
id: RegExp[];
|
|
58
|
+
};
|
|
59
|
+
handler(this: import("rolldown").PluginContext, id: string): Promise<string | {
|
|
60
|
+
code: string;
|
|
61
|
+
map: string | undefined;
|
|
62
|
+
moduleType?: undefined;
|
|
63
|
+
} | {
|
|
64
|
+
map?: undefined;
|
|
65
|
+
code: string;
|
|
66
|
+
moduleType: "js";
|
|
67
|
+
} | null>;
|
|
68
|
+
};
|
|
69
|
+
transform: {
|
|
70
|
+
filter: {
|
|
71
|
+
id: {
|
|
72
|
+
include: RegExp;
|
|
73
|
+
exclude: RegExp[];
|
|
74
|
+
};
|
|
75
|
+
code: RegExp;
|
|
76
|
+
};
|
|
77
|
+
handler(this: import("rolldown").TransformPluginContext, code: string, id: string): Promise<{
|
|
78
|
+
code: string;
|
|
79
|
+
map: string | undefined;
|
|
80
|
+
} | null>;
|
|
81
|
+
};
|
|
82
|
+
/** A watch rebuild: modules that read the changed file's tokens or relations compile again. */
|
|
83
|
+
watchChange(id: string): Promise<void>;
|
|
84
|
+
buildEnd(this: import("rolldown").PluginContext): Promise<void>;
|
|
85
|
+
generateBundle: {
|
|
86
|
+
order: "pre";
|
|
87
|
+
handler(this: import("rolldown").PluginContext, _output: import("rolldown").NormalizedOutputOptions, bundle: OutputBundle): Promise<void>;
|
|
88
|
+
};
|
|
89
|
+
};
|
|
90
|
+
session: () => Promise<Driver>;
|
|
91
|
+
state: {
|
|
92
|
+
root: string;
|
|
93
|
+
stylesheetFile: string;
|
|
94
|
+
};
|
|
95
|
+
filled: Set<string>;
|
|
96
|
+
fill: (bundle: OutputBundle, emitFile: (file: {
|
|
97
|
+
type: "asset";
|
|
98
|
+
fileName: string;
|
|
99
|
+
source: string;
|
|
100
|
+
}) => unknown) => Promise<void>;
|
|
101
|
+
};
|
|
102
|
+
export default function kirana(options?: KiranaRolldownOptions): Plugin;
|
package/src/index.js
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Rolldown host adapter (KRN-INT-001, KRN-INT-002). It selects modules, forwards transform
|
|
3
|
+
* results, emits the stylesheet entry and forwards file changes to the Driver; it never interprets
|
|
4
|
+
* styles (D-028).
|
|
5
|
+
*
|
|
6
|
+
* The Vite adapter is this plugin plus Vite's dev-server hooks, because Vite's plugin API extends
|
|
7
|
+
* Rolldown's: one implementation of the build path serves both hosts. The one difference is CSS:
|
|
8
|
+
* Vite bundles it, so the stylesheet entry is a placeholder Vite processes and the plugin fills in;
|
|
9
|
+
* Rolldown alone does not, so the plugin emits the stylesheet as an asset of its own.
|
|
10
|
+
*/
|
|
11
|
+
import { COMPONENT, Driver } from "@kirana-style/compiler";
|
|
12
|
+
import { ASSET_QUERY, PLACEHOLDER, SCRIPT, STYLE_BEARING, cleanId, createReporter, fillStylesheet, renameAsset, stylesheetPath, } from "@kirana-style/compiler/host";
|
|
13
|
+
import { readFile } from "node:fs/promises";
|
|
14
|
+
import path from "node:path";
|
|
15
|
+
/** A request for part of a Vue, Svelte or Astro component, which carries a query. */
|
|
16
|
+
const COMPONENT_PART = /\.(?:vue|svelte|astro)\?/;
|
|
17
|
+
/**
|
|
18
|
+
* The plugin, with the session and the settings a host that extends it may change (the Vite
|
|
19
|
+
* adapter sets the root from Vite's resolved configuration).
|
|
20
|
+
*/
|
|
21
|
+
export function createKirana(options = {}, host = {}) {
|
|
22
|
+
const state = { root: options.root ?? process.cwd(), stylesheetFile: "" };
|
|
23
|
+
let driver;
|
|
24
|
+
const reporter = createReporter(options);
|
|
25
|
+
const renamed = new Map();
|
|
26
|
+
/** Stylesheet assets filled in place (`Host.renames` false), by output file name. */
|
|
27
|
+
const filled = new Set();
|
|
28
|
+
const development = () => host.development?.() ?? false;
|
|
29
|
+
const fileName = options.fileName ?? "kirana.css";
|
|
30
|
+
const session = () => {
|
|
31
|
+
driver ??= Driver.create({
|
|
32
|
+
root: state.root,
|
|
33
|
+
config: options.config,
|
|
34
|
+
...options.driver,
|
|
35
|
+
});
|
|
36
|
+
return driver;
|
|
37
|
+
};
|
|
38
|
+
const stylesheetFile = () => (state.stylesheetFile ||= stylesheetPath(state.root));
|
|
39
|
+
const plugin = {
|
|
40
|
+
name: "kirana",
|
|
41
|
+
async buildStart() {
|
|
42
|
+
await session();
|
|
43
|
+
},
|
|
44
|
+
load: {
|
|
45
|
+
// No `node_modules` exclusion here: in an installed app the stylesheet entry lives there.
|
|
46
|
+
filter: { id: [/stylesheet\.css/, COMPONENT] },
|
|
47
|
+
async handler(id) {
|
|
48
|
+
// A Vue, Svelte or Astro component is compiled as it is read, before any framework plugin's
|
|
49
|
+
// `transform` — Astro's compiles `.astro` files ahead of every user plugin, so a transform
|
|
50
|
+
// would only ever see its output (design-notes/sfc-masking.md).
|
|
51
|
+
if (COMPONENT.test(id)) {
|
|
52
|
+
// An installed component is compiled only when its package is in `libraries`.
|
|
53
|
+
const compiler = await session();
|
|
54
|
+
if (!compiler.compiles(id))
|
|
55
|
+
return null;
|
|
56
|
+
const source = await readFile(id, "utf8");
|
|
57
|
+
const result = await compiler.transformComponent(id, source);
|
|
58
|
+
for (const dependency of result.dependencies)
|
|
59
|
+
this.addWatchFile(dependency);
|
|
60
|
+
reporter.module(this, id, source, result.diagnostics);
|
|
61
|
+
if (result.cssChanged)
|
|
62
|
+
host.cssChanged?.();
|
|
63
|
+
return result.code === undefined
|
|
64
|
+
? null
|
|
65
|
+
: { code: result.code, map: result.map };
|
|
66
|
+
}
|
|
67
|
+
if (cleanId(id) !== stylesheetFile())
|
|
68
|
+
return null;
|
|
69
|
+
// Rolldown alone: the import is a module naming the file emitted at the end of the build.
|
|
70
|
+
if (!host.bundlesCss) {
|
|
71
|
+
return {
|
|
72
|
+
code: `export default ${JSON.stringify(fileName)}`,
|
|
73
|
+
moduleType: "js",
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
// `?inline` asks for the stylesheet as a string: in development that is the live stylesheet
|
|
77
|
+
// (SvelteKit's server render inlines CSS this way). `?url` and `?raw`, and `?inline` in a
|
|
78
|
+
// build, are Vite's to answer from the file.
|
|
79
|
+
if (ASSET_QUERY.test(id)) {
|
|
80
|
+
return development() && /[?&]inline\b/.test(id)
|
|
81
|
+
? (await session()).stylesheet(false)
|
|
82
|
+
: null;
|
|
83
|
+
}
|
|
84
|
+
return development() ? (await session()).stylesheet(false) : PLACEHOLDER;
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
transform: {
|
|
88
|
+
filter: {
|
|
89
|
+
id: {
|
|
90
|
+
include: SCRIPT,
|
|
91
|
+
// A framework's own requests for a component's parts (`Card.vue?vue&type=script&lang.ts`)
|
|
92
|
+
// hold what it derived from the component, which `load` compiled already.
|
|
93
|
+
// Installed packages are excluded in the handler, which knows `libraries`.
|
|
94
|
+
exclude: [ASSET_QUERY, COMPONENT_PART],
|
|
95
|
+
},
|
|
96
|
+
code: STYLE_BEARING,
|
|
97
|
+
},
|
|
98
|
+
async handler(code, id) {
|
|
99
|
+
const file = cleanId(id);
|
|
100
|
+
const compiler = await session();
|
|
101
|
+
if (!compiler.compiles(file))
|
|
102
|
+
return null;
|
|
103
|
+
const result = compiler.transform(file, code);
|
|
104
|
+
for (const dependency of result.dependencies)
|
|
105
|
+
this.addWatchFile(dependency);
|
|
106
|
+
reporter.module(this, file, code, result.diagnostics);
|
|
107
|
+
if (result.cssChanged)
|
|
108
|
+
host.cssChanged?.();
|
|
109
|
+
return result.code === undefined
|
|
110
|
+
? null
|
|
111
|
+
: { code: result.code, map: result.map };
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
/** A watch rebuild: modules that read the changed file's tokens or relations compile again. */
|
|
115
|
+
async watchChange(id) {
|
|
116
|
+
;
|
|
117
|
+
(await session()).invalidate(id);
|
|
118
|
+
},
|
|
119
|
+
async buildEnd() {
|
|
120
|
+
if (development())
|
|
121
|
+
return;
|
|
122
|
+
reporter.project(this, state.root, (await session()).diagnostics());
|
|
123
|
+
},
|
|
124
|
+
// First, so every plugin that reads CSS assets reads the finished stylesheet: Astro inlines
|
|
125
|
+
// small stylesheets into its pages in its own `generateBundle`. Vite writes HTML later from each
|
|
126
|
+
// chunk's `viteMetadata.importedCss`, which is renamed here along with the asset.
|
|
127
|
+
generateBundle: {
|
|
128
|
+
order: "pre",
|
|
129
|
+
async handler(_output, bundle) {
|
|
130
|
+
if (!host.bundlesCss) {
|
|
131
|
+
const css = (await session()).stylesheet(true);
|
|
132
|
+
this.emitFile({ type: "asset", fileName, source: css });
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
await fill(bundle, this.emitFile.bind(this));
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
/**
|
|
140
|
+
* Fills every stylesheet asset still holding the placeholder, renaming it by content unless the
|
|
141
|
+
* host keeps names, and points the chunks and assets that named it at the new name. A second pass
|
|
142
|
+
* finds nothing to do, so a host may run it again later in `generateBundle` for assets another
|
|
143
|
+
* plugin added after the first pass (Qwik's).
|
|
144
|
+
*/
|
|
145
|
+
const fill = async (bundle, emitFile) => {
|
|
146
|
+
const css = (await session()).stylesheet(true);
|
|
147
|
+
for (const [key, asset] of Object.entries(bundle)) {
|
|
148
|
+
if (asset.type !== "asset")
|
|
149
|
+
continue;
|
|
150
|
+
const source = fillStylesheet(String(asset.source), css);
|
|
151
|
+
if (source === undefined)
|
|
152
|
+
continue;
|
|
153
|
+
if (host.renames === false) {
|
|
154
|
+
asset.source = source;
|
|
155
|
+
filled.add(asset.fileName);
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
const hashed = renamed.get(asset.fileName) ?? renameAsset(asset.fileName, source);
|
|
159
|
+
renamed.set(asset.fileName, hashed);
|
|
160
|
+
delete bundle[key];
|
|
161
|
+
emitFile({ type: "asset", fileName: hashed, source });
|
|
162
|
+
}
|
|
163
|
+
// Every rename so far, not only this pass's: a server bundle names the client's asset.
|
|
164
|
+
for (const [from, to] of renamed) {
|
|
165
|
+
const oldBase = path.posix.basename(from);
|
|
166
|
+
const newBase = path.posix.basename(to);
|
|
167
|
+
for (const file of Object.values(bundle)) {
|
|
168
|
+
if (file.type === "chunk") {
|
|
169
|
+
file.code = file.code.replaceAll(oldBase, newBase);
|
|
170
|
+
const imported = file.viteMetadata?.importedCss;
|
|
171
|
+
if (imported?.delete(from))
|
|
172
|
+
imported.add(to);
|
|
173
|
+
}
|
|
174
|
+
else if (typeof file.source === "string") {
|
|
175
|
+
file.source = file.source.replaceAll(oldBase, newBase);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
return { plugin, session, state, filled, fill };
|
|
181
|
+
}
|
|
182
|
+
export default function kirana(options = {}) {
|
|
183
|
+
return createKirana(options).plugin;
|
|
184
|
+
}
|