@elenajs/bundler 1.0.0-rc.7 → 1.0.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 +5 -4
- package/package.json +12 -13
- package/src/common/load-config.js +7 -0
- package/src/common/validate-config.js +12 -0
- package/src/rollup-build.js +147 -41
package/README.md
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
+
<br/>
|
|
1
2
|
<div align="center">
|
|
2
3
|
<picture>
|
|
3
|
-
<source media="(prefers-color-scheme: dark)" srcset="https://elenajs.com/
|
|
4
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://elenajs.com/elena-v2-dark.png" alt="Elena" width="127" height="156">
|
|
4
5
|
</source>
|
|
5
|
-
<source media="(prefers-color-scheme: light)" srcset="https://elenajs.com/
|
|
6
|
+
<source media="(prefers-color-scheme: light)" srcset="https://elenajs.com/elena-v2.png" alt="Elena" width="127" height="156">
|
|
6
7
|
</source>
|
|
7
|
-
<img src="https://elenajs.com/
|
|
8
|
+
<img src="https://elenajs.com/elena-v2.png" alt="Elena" width="127" height="156">
|
|
8
9
|
</picture>
|
|
9
10
|
|
|
10
|
-
### Bundler for Progressive Web Component libraries built with Elena
|
|
11
|
+
### Bundler for Progressive Web Component libraries built with Elena
|
|
11
12
|
|
|
12
13
|
<br/>
|
|
13
14
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elenajs/bundler",
|
|
3
|
-
"version": "1.0.0
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Bundler for Progressive Web Component libraries built with Elena.",
|
|
5
5
|
"author": "Elena <hi@elenajs.com>",
|
|
6
6
|
"homepage": "https://elenajs.com/",
|
|
@@ -36,27 +36,26 @@
|
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@babel/core": "7.29.0",
|
|
39
|
-
"@babel/preset-env": "7.29.
|
|
39
|
+
"@babel/preset-env": "7.29.2",
|
|
40
40
|
"@custom-elements-manifest/analyzer": "0.11.0",
|
|
41
|
-
"@elenajs/plugin-cem-define": "^1.0.0
|
|
42
|
-
"@elenajs/plugin-cem-prop": "^1.0.0
|
|
43
|
-
"@elenajs/plugin-cem-tag": "^1.0.0
|
|
44
|
-
"@elenajs/plugin-cem-typescript": "^1.0.0
|
|
45
|
-
"@elenajs/plugin-rollup-css": "^1.0.0
|
|
41
|
+
"@elenajs/plugin-cem-define": "^1.0.0",
|
|
42
|
+
"@elenajs/plugin-cem-prop": "^1.0.0",
|
|
43
|
+
"@elenajs/plugin-cem-tag": "^1.0.0",
|
|
44
|
+
"@elenajs/plugin-cem-typescript": "^1.0.0",
|
|
45
|
+
"@elenajs/plugin-rollup-css": "^1.0.0",
|
|
46
46
|
"@rollup/plugin-babel": "7.0.0",
|
|
47
47
|
"@rollup/plugin-node-resolve": "16.0.3",
|
|
48
48
|
"@rollup/plugin-terser": "1.0.0",
|
|
49
49
|
"@rollup/plugin-typescript": "12.3.0",
|
|
50
50
|
"custom-element-jsx-integration": "1.6.0",
|
|
51
|
-
"globby": "16.
|
|
52
|
-
"rollup": "4.
|
|
53
|
-
"rollup-plugin-minify-html-literals-v3": "1.3.4",
|
|
51
|
+
"globby": "16.2.0",
|
|
52
|
+
"rollup": "4.60.1",
|
|
54
53
|
"rollup-plugin-summary": "3.0.1",
|
|
55
54
|
"tslib": "2.8.1",
|
|
56
|
-
"typescript": "
|
|
55
|
+
"typescript": "6.0.2"
|
|
57
56
|
},
|
|
58
57
|
"devDependencies": {
|
|
59
|
-
"vitest": "4.1.
|
|
58
|
+
"vitest": "4.1.2"
|
|
60
59
|
},
|
|
61
|
-
"gitHead": "
|
|
60
|
+
"gitHead": "ef466853f84deb6559ea66b54cd85b7e043e9f36"
|
|
62
61
|
}
|
|
@@ -9,6 +9,8 @@ import { validateConfig } from "./validate-config.js";
|
|
|
9
9
|
* @property {string} [dir] Output directory for individual modules, CSS, and CEM artifacts.
|
|
10
10
|
* @property {string} [format] Rollup output format (default: `"esm"`).
|
|
11
11
|
* @property {boolean} [sourcemap] Whether to emit sourcemaps (default: `true`).
|
|
12
|
+
* @property {string} [filename] Output filename for the bundle (default: `"bundle.js"`).
|
|
13
|
+
* The CSS bundle filename is derived by replacing the `.js` extension with `.css`.
|
|
12
14
|
*/
|
|
13
15
|
|
|
14
16
|
/**
|
|
@@ -32,6 +34,10 @@ import { validateConfig } from "./validate-config.js";
|
|
|
32
34
|
* `{ ecma: 2020, module: true }`.
|
|
33
35
|
* @property {string|false} [banner] Banner comment prepended to every output file. Use a
|
|
34
36
|
* `@license` JSDoc tag so minifiers preserve it. Set to `false` (default) to omit.
|
|
37
|
+
* @property {"auto"|"scoped"} [registration] Controls how components are registered.
|
|
38
|
+
* `"auto"` (default) preserves `.define()` calls in the output.
|
|
39
|
+
* `"scoped"` strips `.define()` calls and generates a `register.js` helper
|
|
40
|
+
* with a `defineAll(registry?)` function for scoped registry usage.
|
|
35
41
|
*/
|
|
36
42
|
|
|
37
43
|
/** @type {Required<ElenaConfig>} */
|
|
@@ -44,6 +50,7 @@ const DEFAULTS = {
|
|
|
44
50
|
target: false,
|
|
45
51
|
terser: { ecma: 2020, module: true },
|
|
46
52
|
banner: false,
|
|
53
|
+
registration: "auto",
|
|
47
54
|
};
|
|
48
55
|
|
|
49
56
|
/**
|
|
@@ -9,6 +9,7 @@ const KNOWN_KEYS = new Set([
|
|
|
9
9
|
"target",
|
|
10
10
|
"terser",
|
|
11
11
|
"banner",
|
|
12
|
+
"registration",
|
|
12
13
|
]);
|
|
13
14
|
|
|
14
15
|
/**
|
|
@@ -43,6 +44,9 @@ export function validateConfig(config) {
|
|
|
43
44
|
if (config.output.sourcemap !== undefined && typeof config.output.sourcemap !== "boolean") {
|
|
44
45
|
throw new Error(`░█ [ELENA]: Invalid config: "output.sourcemap" must be a boolean.`);
|
|
45
46
|
}
|
|
47
|
+
if (config.output.filename !== undefined && typeof config.output.filename !== "string") {
|
|
48
|
+
throw new Error(`░█ [ELENA]: Invalid config: "output.filename" must be a string.`);
|
|
49
|
+
}
|
|
46
50
|
}
|
|
47
51
|
|
|
48
52
|
if (config.bundle !== undefined && typeof config.bundle !== "string" && config.bundle !== false) {
|
|
@@ -80,4 +84,12 @@ export function validateConfig(config) {
|
|
|
80
84
|
if (config.banner !== undefined && typeof config.banner !== "string" && config.banner !== false) {
|
|
81
85
|
throw new Error(`░█ [ELENA]: Invalid config: "banner" must be a string or false.`);
|
|
82
86
|
}
|
|
87
|
+
|
|
88
|
+
if (
|
|
89
|
+
config.registration !== undefined &&
|
|
90
|
+
config.registration !== "auto" &&
|
|
91
|
+
config.registration !== "scoped"
|
|
92
|
+
) {
|
|
93
|
+
throw new Error(`░█ [ELENA]: Invalid config: "registration" must be "auto" or "scoped".`);
|
|
94
|
+
}
|
|
83
95
|
}
|
package/src/rollup-build.js
CHANGED
|
@@ -17,7 +17,6 @@ import { rollup, watch } from "rollup";
|
|
|
17
17
|
import resolve from "@rollup/plugin-node-resolve";
|
|
18
18
|
import terser from "@rollup/plugin-terser";
|
|
19
19
|
import typescript from "@rollup/plugin-typescript";
|
|
20
|
-
import minifyHtmlLiterals from "rollup-plugin-minify-html-literals-v3";
|
|
21
20
|
import summary from "rollup-plugin-summary";
|
|
22
21
|
import {
|
|
23
22
|
cssPlugin,
|
|
@@ -29,10 +28,118 @@ import { color } from "./common/color.js";
|
|
|
29
28
|
import babel from "@rollup/plugin-babel";
|
|
30
29
|
|
|
31
30
|
const TREESHAKE = {
|
|
32
|
-
moduleSideEffects: false,
|
|
33
31
|
propertyReadSideEffects: false,
|
|
34
32
|
};
|
|
35
33
|
|
|
34
|
+
const DEFINE_CALL = /^\s*\w+\.define\(\);\s*$/gm;
|
|
35
|
+
const SIDE_EFFECT_IMPORT = /^\s*import\s+["']([^"']+)["']\s*;\s*$/gm;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Rollup plugin that strips `.define()` calls and side-effect-only
|
|
39
|
+
* imports that resolve to Elena component modules (files containing
|
|
40
|
+
* `.define()` calls). Non-component side-effect imports (polyfills,
|
|
41
|
+
* CSS, setup scripts) are preserved.
|
|
42
|
+
*
|
|
43
|
+
* Used with `registration: "scoped"`.
|
|
44
|
+
*
|
|
45
|
+
* @returns {import("rollup").Plugin}
|
|
46
|
+
*/
|
|
47
|
+
function stripRegistrationPlugin() {
|
|
48
|
+
return {
|
|
49
|
+
name: "elena-strip-registration",
|
|
50
|
+
async transform(code, id) {
|
|
51
|
+
if (!id.endsWith(".js") && !id.endsWith(".ts")) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let stripped = code.replace(DEFINE_CALL, "");
|
|
56
|
+
|
|
57
|
+
// Check each side-effect import: only strip if the resolved
|
|
58
|
+
// module contains a .define() call (i.e. it is a component).
|
|
59
|
+
const importMatches = [...stripped.matchAll(SIDE_EFFECT_IMPORT)];
|
|
60
|
+
for (const match of importMatches) {
|
|
61
|
+
const specifier = match[1];
|
|
62
|
+
const resolved = await this.resolve(specifier, id);
|
|
63
|
+
if (!resolved) {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
const module = await this.load({ id: resolved.id });
|
|
67
|
+
if (module.code && /^\s*\w+\.define\(\);\s*$/m.test(module.code)) {
|
|
68
|
+
stripped = stripped.replace(match[0], "");
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (stripped === code) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
return { code: stripped, map: null };
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Rollup plugin that emits a `register.js` module exporting a
|
|
82
|
+
* `defineAll(registry?)` helper and re-exporting all component classes.
|
|
83
|
+
* Used with `registration: "scoped"`.
|
|
84
|
+
*
|
|
85
|
+
* @param {string} src - Source directory (e.g. `"src"`).
|
|
86
|
+
* @returns {import("rollup").Plugin}
|
|
87
|
+
*/
|
|
88
|
+
function emitRegisterPlugin(src) {
|
|
89
|
+
return {
|
|
90
|
+
name: "elena-emit-register",
|
|
91
|
+
generateBundle(_, bundle) {
|
|
92
|
+
// Collect component modules from the emitted chunks.
|
|
93
|
+
const components = [];
|
|
94
|
+
for (const [fileName, chunk] of Object.entries(bundle)) {
|
|
95
|
+
if (chunk.type !== "chunk") {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
// Check the original module source for static tagName.
|
|
99
|
+
for (const moduleId of Object.keys(chunk.modules)) {
|
|
100
|
+
const info = this.getModuleInfo(moduleId);
|
|
101
|
+
if (!info || !info.code) {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (/static\s+tagName\s*=/.test(info.code)) {
|
|
105
|
+
const match = info.code.match(/(?:export\s+default\s+)?class\s+(\w+)/);
|
|
106
|
+
if (match) {
|
|
107
|
+
components.push({ className: match[1], importPath: `./${fileName}` });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (components.length === 0) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const imports = components
|
|
118
|
+
.map(c => `import { default as ${c.className} } from "${c.importPath}";`)
|
|
119
|
+
.join("\n");
|
|
120
|
+
const definealls = components.map(c => ` ${c.className}.define(registry);`).join("\n");
|
|
121
|
+
const exports = components.map(c => c.className).join(", ");
|
|
122
|
+
|
|
123
|
+
const source = [
|
|
124
|
+
imports,
|
|
125
|
+
"",
|
|
126
|
+
`export function defineAll(registry) {`,
|
|
127
|
+
definealls,
|
|
128
|
+
`}`,
|
|
129
|
+
"",
|
|
130
|
+
`export { ${exports} };`,
|
|
131
|
+
"",
|
|
132
|
+
].join("\n");
|
|
133
|
+
|
|
134
|
+
this.emitFile({
|
|
135
|
+
type: "asset",
|
|
136
|
+
fileName: "register.js",
|
|
137
|
+
source,
|
|
138
|
+
});
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
36
143
|
/**
|
|
37
144
|
* Suppress noisy Rollup warnings.
|
|
38
145
|
*
|
|
@@ -49,7 +156,7 @@ function onwarn(warning, warn) {
|
|
|
49
156
|
/**
|
|
50
157
|
* Build the plugin list for a single Rollup build target.
|
|
51
158
|
*
|
|
52
|
-
* @param {{ src: string; outdir: string; hasSummary: boolean; includeCssBundle: boolean; extraPlugins?: import("rollup").Plugin[]; hasTs?: boolean; target?: string | string[] | false }} opts
|
|
159
|
+
* @param {{ src: string; outdir: string; hasSummary: boolean; includeCssBundle: boolean; cssBundleFilename?: string; extraPlugins?: import("rollup").Plugin[]; hasTs?: boolean; target?: string | string[] | false }} opts
|
|
53
160
|
* @returns {import("rollup").Plugin[]}
|
|
54
161
|
*/
|
|
55
162
|
function buildPlugins({
|
|
@@ -57,6 +164,7 @@ function buildPlugins({
|
|
|
57
164
|
outdir,
|
|
58
165
|
hasSummary,
|
|
59
166
|
includeCssBundle,
|
|
167
|
+
cssBundleFilename = "bundle.css",
|
|
60
168
|
extraPlugins = [],
|
|
61
169
|
hasTs = false,
|
|
62
170
|
target = false,
|
|
@@ -87,25 +195,10 @@ function buildPlugins({
|
|
|
87
195
|
);
|
|
88
196
|
}
|
|
89
197
|
|
|
90
|
-
plugins.push(
|
|
91
|
-
cssStaticStylesPlugin(),
|
|
92
|
-
minifyHtmlLiterals({
|
|
93
|
-
options: {
|
|
94
|
-
shouldMinify: template => {
|
|
95
|
-
const tag = template.tag && template.tag.toLowerCase();
|
|
96
|
-
return (
|
|
97
|
-
(tag && (tag.includes("html") || tag.includes("svg"))) ||
|
|
98
|
-
template.parts.some(({ text }) => /<[a-z]/i.test(text))
|
|
99
|
-
);
|
|
100
|
-
},
|
|
101
|
-
},
|
|
102
|
-
}),
|
|
103
|
-
terser(terserOpts),
|
|
104
|
-
cssPlugin(src)
|
|
105
|
-
);
|
|
198
|
+
plugins.push(cssStaticStylesPlugin(), terser(terserOpts), cssPlugin(src));
|
|
106
199
|
|
|
107
200
|
if (includeCssBundle) {
|
|
108
|
-
plugins.push(cssBundlePlugin(src,
|
|
201
|
+
plugins.push(cssBundlePlugin(src, cssBundleFilename));
|
|
109
202
|
}
|
|
110
203
|
|
|
111
204
|
plugins.push(...extraPlugins);
|
|
@@ -129,10 +222,13 @@ export function createRollupConfig(options = {}) {
|
|
|
129
222
|
const format = options.output?.format ?? "esm";
|
|
130
223
|
const sourcemap = options.output?.sourcemap ?? true;
|
|
131
224
|
let bundle = options.bundle !== undefined ? options.bundle : "src/index.js";
|
|
225
|
+
const bundleFilename = options.output?.filename ?? "bundle.js";
|
|
226
|
+
const cssBundleFilename = bundleFilename.replace(/\.js$/, ".css");
|
|
132
227
|
const extraPlugins = options.plugins ?? [];
|
|
133
228
|
const target = options.target ?? false;
|
|
134
229
|
const terserOpts = options.terser ?? { ecma: 2020, module: true };
|
|
135
230
|
const banner = options.banner || undefined;
|
|
231
|
+
const scoped = options.registration === "scoped";
|
|
136
232
|
|
|
137
233
|
if (!existsSync(src)) {
|
|
138
234
|
throw new Error(
|
|
@@ -161,19 +257,25 @@ export function createRollupConfig(options = {}) {
|
|
|
161
257
|
);
|
|
162
258
|
}
|
|
163
259
|
|
|
260
|
+
const scopedPlugins = scoped ? [stripRegistrationPlugin(), emitRegisterPlugin(src)] : [];
|
|
261
|
+
|
|
164
262
|
const configs = [
|
|
165
263
|
{
|
|
166
264
|
input: entries,
|
|
167
|
-
plugins:
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
265
|
+
plugins: [
|
|
266
|
+
...scopedPlugins,
|
|
267
|
+
...buildPlugins({
|
|
268
|
+
src,
|
|
269
|
+
outdir,
|
|
270
|
+
hasSummary: false,
|
|
271
|
+
includeCssBundle: true,
|
|
272
|
+
cssBundleFilename,
|
|
273
|
+
extraPlugins,
|
|
274
|
+
hasTs,
|
|
275
|
+
target,
|
|
276
|
+
terserOpts,
|
|
277
|
+
}),
|
|
278
|
+
],
|
|
177
279
|
output: {
|
|
178
280
|
...(banner && {
|
|
179
281
|
banner: chunk => (chunk.fileName === "index.js" ? banner : ""),
|
|
@@ -190,19 +292,23 @@ export function createRollupConfig(options = {}) {
|
|
|
190
292
|
];
|
|
191
293
|
|
|
192
294
|
if (bundle) {
|
|
295
|
+
const bundleScoped = scoped ? [stripRegistrationPlugin()] : [];
|
|
193
296
|
configs.push({
|
|
194
297
|
input: bundle,
|
|
195
|
-
plugins:
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
298
|
+
plugins: [
|
|
299
|
+
...bundleScoped,
|
|
300
|
+
...buildPlugins({
|
|
301
|
+
src,
|
|
302
|
+
outdir,
|
|
303
|
+
hasSummary: true,
|
|
304
|
+
includeCssBundle: false,
|
|
305
|
+
extraPlugins,
|
|
306
|
+
hasTs,
|
|
307
|
+
target,
|
|
308
|
+
terserOpts,
|
|
309
|
+
}),
|
|
310
|
+
],
|
|
311
|
+
output: { banner, format, sourcemap, file: `${outdir}/${bundleFilename}` },
|
|
206
312
|
preserveEntrySignatures: "strict",
|
|
207
313
|
treeshake: TREESHAKE,
|
|
208
314
|
onwarn,
|