svelte-bundle 0.0.23 → 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 +3 -1
- package/bundle.js +308 -164
- package/cli.js +166 -92
- package/package.json +15 -9
package/ReadMe.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
> ⚠️ **NOTICE:** While this tool is currently functional, it has not nearly been battle-tested enough to ensure it works in most use-cases.
|
|
2
2
|
|
|
3
|
+

|
|
4
|
+
|
|
3
5
|
## Usage
|
|
4
6
|
This tool can be used with `npx`:
|
|
5
7
|
```bash
|
|
@@ -33,4 +35,4 @@ I noticed through a lot of google searches I wasn't the only one looking for a s
|
|
|
33
35
|
- [x] Tests and CI integration.
|
|
34
36
|
- [x] Handle CSS and assets within the bundled file.
|
|
35
37
|
- [x] Add tailwindcss support, **this is in beta**.
|
|
36
|
-
- [x] Implement error handling and more robust validation.
|
|
38
|
+
- [x] Implement error handling and more robust validation.
|
package/bundle.js
CHANGED
|
@@ -1,186 +1,330 @@
|
|
|
1
1
|
// bundle.js
|
|
2
|
-
import fs from
|
|
3
|
-
import path from
|
|
4
|
-
import { rollup } from
|
|
5
|
-
import svelte from
|
|
6
|
-
import resolve from
|
|
7
|
-
import commonjs from
|
|
8
|
-
import css from
|
|
9
|
-
import terser from
|
|
10
|
-
import postcss from
|
|
11
|
-
import tailwindcss from
|
|
12
|
-
import autoprefixer from
|
|
13
|
-
import cssnano from
|
|
14
|
-
import { fileURLToPath } from
|
|
15
|
-
import { createRequire } from
|
|
2
|
+
import fs from "fs/promises";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { rollup } from "rollup";
|
|
5
|
+
import svelte from "rollup-plugin-svelte";
|
|
6
|
+
import resolve from "@rollup/plugin-node-resolve";
|
|
7
|
+
import commonjs from "@rollup/plugin-commonjs";
|
|
8
|
+
import css from "rollup-plugin-css-only";
|
|
9
|
+
import terser from "@rollup/plugin-terser";
|
|
10
|
+
import postcss from "postcss";
|
|
11
|
+
import tailwindcss from "tailwindcss";
|
|
12
|
+
import autoprefixer from "autoprefixer";
|
|
13
|
+
import cssnano from "cssnano";
|
|
14
|
+
import { fileURLToPath } from "url";
|
|
15
|
+
import { createRequire } from "module";
|
|
16
16
|
|
|
17
17
|
const require = createRequire(import.meta.url);
|
|
18
18
|
const __filename = fileURLToPath(import.meta.url);
|
|
19
19
|
const __dirname = path.dirname(__filename);
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Creates PostCSS plugins array based on Tailwind configuration
|
|
23
|
+
*/
|
|
24
|
+
function createPostCSSPlugins(useTailwind, tailwindConfig, svelteFilePath) {
|
|
25
|
+
const plugins = [];
|
|
23
26
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
+
if (useTailwind) {
|
|
28
|
+
plugins.push(
|
|
29
|
+
tailwindcss(
|
|
30
|
+
tailwindConfig || {
|
|
31
|
+
content: [svelteFilePath],
|
|
32
|
+
theme: { extend: {} },
|
|
33
|
+
plugins: [],
|
|
34
|
+
},
|
|
35
|
+
),
|
|
36
|
+
);
|
|
37
|
+
}
|
|
27
38
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
39
|
+
plugins.push(
|
|
40
|
+
autoprefixer(),
|
|
41
|
+
cssnano({
|
|
42
|
+
preset: [
|
|
43
|
+
"default",
|
|
44
|
+
{
|
|
45
|
+
discardComments: { removeAll: true },
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
}),
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
return plugins;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Generates global Tailwind CSS if enabled
|
|
56
|
+
*/
|
|
57
|
+
async function generateGlobalCSS(useTailwind, postcssPlugins) {
|
|
58
|
+
if (!useTailwind) return "";
|
|
59
|
+
|
|
60
|
+
const tailwindCss = `
|
|
61
|
+
@tailwind base;
|
|
62
|
+
@tailwind components;
|
|
63
|
+
@tailwind utilities;
|
|
64
|
+
`;
|
|
65
|
+
|
|
66
|
+
const processedCss = await postcss(postcssPlugins).process(tailwindCss, {
|
|
67
|
+
from: undefined,
|
|
68
|
+
});
|
|
69
|
+
return processedCss.css;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Creates style preprocessor for Svelte components
|
|
74
|
+
*/
|
|
75
|
+
function createStylePreprocessor(useTailwind, postcssPlugins) {
|
|
76
|
+
if (!useTailwind) return undefined;
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
style: async ({ content }) => {
|
|
80
|
+
if (!content) return { code: "" };
|
|
81
|
+
const result = await postcss(postcssPlugins).process(content, {
|
|
82
|
+
from: undefined,
|
|
83
|
+
});
|
|
84
|
+
return { code: result.css };
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Creates Rollup configuration for Svelte compilation
|
|
91
|
+
*/
|
|
92
|
+
function createSvelteConfig(options) {
|
|
93
|
+
const { generate, preprocess, emitCss = true } = options;
|
|
71
94
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
95
|
+
return {
|
|
96
|
+
compilerOptions: {
|
|
97
|
+
generate,
|
|
98
|
+
css: "injected", // Svelte 5: use 'injected' for CSS handling
|
|
99
|
+
},
|
|
100
|
+
emitCss,
|
|
101
|
+
preprocess,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Builds SSR bundle and renders component to HTML
|
|
107
|
+
*/
|
|
108
|
+
async function buildSSRBundle(
|
|
109
|
+
svelteFilePath,
|
|
110
|
+
preprocessor,
|
|
111
|
+
__dirname,
|
|
112
|
+
verbose = false,
|
|
113
|
+
) {
|
|
114
|
+
let cssText = "";
|
|
115
|
+
|
|
116
|
+
const ssrBundle = await rollup({
|
|
117
|
+
input: svelteFilePath,
|
|
118
|
+
onwarn: (warning, warn) => {
|
|
119
|
+
// Suppress warnings unless verbose mode is enabled
|
|
120
|
+
if (verbose) warn(warning);
|
|
121
|
+
},
|
|
122
|
+
plugins: [
|
|
123
|
+
svelte({
|
|
124
|
+
...createSvelteConfig({
|
|
125
|
+
generate: "server",
|
|
126
|
+
preprocess: preprocessor,
|
|
85
127
|
emitCss: true,
|
|
86
|
-
preprocess: useTailwind ? {
|
|
87
|
-
style: async ({ content }) => {
|
|
88
|
-
if (!content) return { code: '' };
|
|
89
|
-
const result = await postcss(postcssPlugins)
|
|
90
|
-
.process(content, { from: undefined });
|
|
91
|
-
return { code: result.css };
|
|
92
|
-
}
|
|
93
|
-
} : undefined
|
|
94
|
-
}),
|
|
95
|
-
css({
|
|
96
|
-
output: function(styles) {
|
|
97
|
-
cssText = styles;
|
|
98
|
-
}
|
|
99
128
|
}),
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
129
|
+
onwarn: (warning, handler) => {
|
|
130
|
+
// Suppress svelte plugin warnings unless verbose mode is enabled
|
|
131
|
+
if (verbose) handler(warning);
|
|
132
|
+
},
|
|
133
|
+
}),
|
|
134
|
+
css({
|
|
135
|
+
output: (styles) => {
|
|
136
|
+
cssText = styles;
|
|
137
|
+
},
|
|
138
|
+
}),
|
|
139
|
+
resolve({
|
|
140
|
+
browser: false, // SSR bundle should use Node resolution
|
|
141
|
+
dedupe: ["svelte"],
|
|
142
|
+
}),
|
|
143
|
+
commonjs(),
|
|
144
|
+
],
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
const tempDir = path.join(__dirname, ".temp");
|
|
148
|
+
await fs.mkdir(tempDir, { recursive: true });
|
|
149
|
+
const tempSSRFile = path.join(tempDir, "ssr-bundle.js");
|
|
150
|
+
|
|
151
|
+
await ssrBundle.write({
|
|
152
|
+
file: tempSSRFile,
|
|
153
|
+
format: "es",
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// Svelte 5: Import both the component and render function
|
|
157
|
+
const { default: App } = await import(`file://${tempSSRFile}`);
|
|
158
|
+
const { render } = await import("svelte/server");
|
|
159
|
+
|
|
160
|
+
// Svelte 5: Use render function instead of App.render()
|
|
161
|
+
const { body, head } = render(App, { props: {} });
|
|
162
|
+
|
|
163
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
164
|
+
|
|
165
|
+
return { html: body, head: head || "", cssText };
|
|
166
|
+
}
|
|
110
167
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
168
|
+
/**
|
|
169
|
+
* Builds client-side bundle for hydration
|
|
170
|
+
*/
|
|
171
|
+
async function buildClientBundle(
|
|
172
|
+
svelteFilePath,
|
|
173
|
+
preprocessor,
|
|
174
|
+
verbose = false,
|
|
175
|
+
) {
|
|
176
|
+
let cssText = "";
|
|
177
|
+
|
|
178
|
+
// Create a wrapper entry that imports and hydrates the component
|
|
179
|
+
const tempDir = path.join(__dirname, ".temp");
|
|
180
|
+
await fs.mkdir(tempDir, { recursive: true });
|
|
181
|
+
const wrapperPath = path.join(tempDir, "wrapper.js");
|
|
182
|
+
|
|
183
|
+
// Create wrapper that imports component and hydrate function
|
|
184
|
+
await fs.writeFile(
|
|
185
|
+
wrapperPath,
|
|
186
|
+
`
|
|
187
|
+
import { hydrate } from 'svelte';
|
|
188
|
+
import Component from '${svelteFilePath.replace(/\\/g, "/")}';
|
|
189
|
+
|
|
190
|
+
hydrate(Component, {
|
|
191
|
+
target: document.getElementById('app')
|
|
124
192
|
});
|
|
193
|
+
`,
|
|
194
|
+
);
|
|
125
195
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
svelte({
|
|
138
|
-
compilerOptions: {
|
|
139
|
-
hydratable: true,
|
|
140
|
-
css: false
|
|
141
|
-
},
|
|
196
|
+
const clientBundle = await rollup({
|
|
197
|
+
input: wrapperPath,
|
|
198
|
+
onwarn: (warning, warn) => {
|
|
199
|
+
// Suppress warnings unless verbose mode is enabled
|
|
200
|
+
if (verbose) warn(warning);
|
|
201
|
+
},
|
|
202
|
+
plugins: [
|
|
203
|
+
svelte({
|
|
204
|
+
...createSvelteConfig({
|
|
205
|
+
generate: "client",
|
|
206
|
+
preprocess: preprocessor,
|
|
142
207
|
emitCss: true,
|
|
143
|
-
preprocess: useTailwind ? {
|
|
144
|
-
style: async ({ content }) => {
|
|
145
|
-
if (!content) return { code: '' };
|
|
146
|
-
const result = await postcss(postcssPlugins)
|
|
147
|
-
.process(content, { from: undefined });
|
|
148
|
-
return { code: result.css };
|
|
149
|
-
}
|
|
150
|
-
} : undefined
|
|
151
208
|
}),
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
209
|
+
onwarn: (warning, handler) => {
|
|
210
|
+
// Suppress svelte plugin warnings unless verbose mode is enabled
|
|
211
|
+
if (verbose) handler(warning);
|
|
212
|
+
},
|
|
213
|
+
}),
|
|
214
|
+
css({
|
|
215
|
+
output: (styles) => {
|
|
216
|
+
cssText = styles;
|
|
217
|
+
},
|
|
218
|
+
}),
|
|
219
|
+
resolve({
|
|
220
|
+
browser: true,
|
|
221
|
+
dedupe: ["svelte"],
|
|
222
|
+
}),
|
|
223
|
+
commonjs(),
|
|
224
|
+
terser(),
|
|
225
|
+
],
|
|
226
|
+
});
|
|
167
227
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
228
|
+
const {
|
|
229
|
+
output: [{ code }],
|
|
230
|
+
} = await clientBundle.generate({
|
|
231
|
+
format: "iife",
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
// Clean up temp files
|
|
235
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
236
|
+
|
|
237
|
+
return { code, cssText };
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Generates final HTML document
|
|
242
|
+
*/
|
|
243
|
+
function generateHTML(ssrHtml, clientCode, globalCss, componentCss) {
|
|
244
|
+
return `<!DOCTYPE html>
|
|
245
|
+
<html lang="en">
|
|
246
|
+
<head>
|
|
247
|
+
<meta charset="UTF-8">
|
|
248
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
249
|
+
<title>Static Svelte App</title>
|
|
250
|
+
<style>${globalCss}${componentCss}</style>
|
|
251
|
+
</head>
|
|
252
|
+
<body>
|
|
253
|
+
<div id="app">${ssrHtml}</div>
|
|
254
|
+
<script>
|
|
255
|
+
${clientCode}
|
|
256
|
+
</script>
|
|
257
|
+
</body>
|
|
258
|
+
</html>`;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Main build function - bundles a Svelte file into a static HTML file
|
|
263
|
+
* @param {string} svelteFilePath - Path to the input Svelte file
|
|
264
|
+
* @param {string} outputDir - Directory for the output HTML file
|
|
265
|
+
* @param {Object} options - Build options
|
|
266
|
+
* @param {boolean} options.useTailwind - Enable Tailwind CSS processing
|
|
267
|
+
* @param {Object} options.tailwindConfig - Custom Tailwind configuration
|
|
268
|
+
* @param {boolean} options.verbose - Show detailed build output and warnings
|
|
269
|
+
*/
|
|
270
|
+
export async function buildStaticFile(svelteFilePath, outputDir, options = {}) {
|
|
271
|
+
const {
|
|
272
|
+
useTailwind = false,
|
|
273
|
+
tailwindConfig = null,
|
|
274
|
+
verbose = false,
|
|
275
|
+
} = options;
|
|
175
276
|
|
|
176
|
-
|
|
177
|
-
|
|
277
|
+
// Suppress console warnings unless verbose mode is enabled
|
|
278
|
+
const originalConsoleWarn = console.warn;
|
|
178
279
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
280
|
+
if (!verbose) {
|
|
281
|
+
console.warn = () => {};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
try {
|
|
285
|
+
await fs.mkdir(outputDir, { recursive: true });
|
|
286
|
+
|
|
287
|
+
const postcssPlugins = createPostCSSPlugins(
|
|
288
|
+
useTailwind,
|
|
289
|
+
tailwindConfig,
|
|
290
|
+
svelteFilePath,
|
|
291
|
+
);
|
|
292
|
+
const preprocessor = createStylePreprocessor(useTailwind, postcssPlugins);
|
|
293
|
+
|
|
294
|
+
// Generate global Tailwind CSS
|
|
295
|
+
const globalCss = await generateGlobalCSS(useTailwind, postcssPlugins);
|
|
296
|
+
|
|
297
|
+
// Build SSR bundle and render to HTML
|
|
298
|
+
const { html: ssrHtml, cssText: ssrCss } = await buildSSRBundle(
|
|
299
|
+
svelteFilePath,
|
|
300
|
+
preprocessor,
|
|
301
|
+
__dirname,
|
|
302
|
+
verbose,
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
// Build client bundle for hydration
|
|
306
|
+
const { code: clientCode, cssText: clientCss } = await buildClientBundle(
|
|
307
|
+
svelteFilePath,
|
|
308
|
+
preprocessor,
|
|
309
|
+
verbose,
|
|
310
|
+
);
|
|
311
|
+
|
|
312
|
+
// Combine CSS from both bundles
|
|
313
|
+
const combinedCss = ssrCss || clientCss;
|
|
314
|
+
|
|
315
|
+
// Generate final HTML
|
|
316
|
+
const finalHtml = generateHTML(ssrHtml, clientCode, globalCss, combinedCss);
|
|
317
|
+
|
|
318
|
+
// Write output file
|
|
319
|
+
const outputPath = path.join(outputDir, "output.html");
|
|
320
|
+
await fs.writeFile(outputPath, finalHtml, "utf-8");
|
|
182
321
|
} catch (error) {
|
|
183
|
-
console.error(
|
|
322
|
+
console.error("Build error:", error);
|
|
184
323
|
throw error;
|
|
324
|
+
} finally {
|
|
325
|
+
// Restore original console methods
|
|
326
|
+
if (!verbose) {
|
|
327
|
+
console.warn = originalConsoleWarn;
|
|
328
|
+
}
|
|
185
329
|
}
|
|
186
|
-
}
|
|
330
|
+
}
|
package/cli.js
CHANGED
|
@@ -1,152 +1,226 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { program } from
|
|
3
|
-
import chalk from
|
|
4
|
-
import path from
|
|
5
|
-
import fs from
|
|
6
|
-
import { createInterface } from
|
|
7
|
-
import { buildStaticFile } from
|
|
8
|
-
import { fileURLToPath } from
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
import { readFile } from 'fs/promises';
|
|
2
|
+
import { program } from "commander";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import fs from "fs/promises";
|
|
6
|
+
import { createInterface } from "readline";
|
|
7
|
+
import { buildStaticFile } from "./bundle.js";
|
|
8
|
+
import { fileURLToPath } from "url";
|
|
9
|
+
import { readFile } from "fs/promises";
|
|
10
|
+
|
|
12
11
|
const __filename = fileURLToPath(import.meta.url);
|
|
13
12
|
const __dirname = path.dirname(__filename);
|
|
14
13
|
|
|
15
14
|
// Read package.json
|
|
16
15
|
const packageJson = JSON.parse(
|
|
17
|
-
await readFile(
|
|
18
|
-
new URL('./package.json', import.meta.url)
|
|
19
|
-
)
|
|
16
|
+
await readFile(new URL("./package.json", import.meta.url)),
|
|
20
17
|
);
|
|
21
18
|
|
|
22
19
|
const rl = createInterface({
|
|
23
20
|
input: process.stdin,
|
|
24
|
-
output: process.stdout
|
|
21
|
+
output: process.stdout,
|
|
25
22
|
});
|
|
26
23
|
|
|
27
|
-
const question = (query) =>
|
|
24
|
+
const question = (query) =>
|
|
25
|
+
new Promise((resolve) => rl.question(query, resolve));
|
|
28
26
|
|
|
29
|
-
//
|
|
27
|
+
// Validate Node.js version
|
|
30
28
|
const nodeVersion = process.versions.node;
|
|
31
|
-
const [major] = nodeVersion.split(
|
|
29
|
+
const [major] = nodeVersion.split(".").map(Number);
|
|
32
30
|
if (major < 18) {
|
|
33
|
-
console.error(
|
|
31
|
+
console.error(
|
|
32
|
+
chalk.red(`✗ Error: Node.js 18+ required (current: ${nodeVersion})`),
|
|
33
|
+
);
|
|
34
34
|
process.exit(1);
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
// Display banner
|
|
38
|
+
function displayBanner() {
|
|
39
|
+
console.log(chalk.cyan.bold("\n╔════════════════════════════════════════╗"));
|
|
40
|
+
console.log(
|
|
41
|
+
chalk.cyan.bold("║ ") +
|
|
42
|
+
chalk.white.bold("SVELTE BUNDLE") +
|
|
43
|
+
chalk.cyan.bold(" ║"),
|
|
44
|
+
);
|
|
45
|
+
console.log(chalk.cyan.bold("╚════════════════════════════════════════╝\n"));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Configure CLI
|
|
37
49
|
program
|
|
38
|
-
.name(
|
|
39
|
-
.description(packageJson.description)
|
|
50
|
+
.name("svelte-bundle")
|
|
51
|
+
.description(chalk.gray(packageJson.description))
|
|
40
52
|
.version(packageJson.version)
|
|
41
|
-
.requiredOption(
|
|
42
|
-
.option(
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
53
|
+
.requiredOption("-i, --input <path>", "Input Svelte file")
|
|
54
|
+
.option(
|
|
55
|
+
"-o, --output <path>",
|
|
56
|
+
"Output directory (default: current directory)",
|
|
57
|
+
)
|
|
58
|
+
.option("--tw", "Enable Tailwind CSS processing")
|
|
59
|
+
.option("--tw-config <path>", "Path to custom Tailwind config")
|
|
60
|
+
.option("-f, --force", "Force overwrite without confirmation")
|
|
61
|
+
.option("-v, --verbose", "Show detailed build output and warnings")
|
|
62
|
+
.addHelpText(
|
|
63
|
+
"after",
|
|
64
|
+
`
|
|
65
|
+
${chalk.cyan("Examples:")}
|
|
66
|
+
${chalk.gray("# Basic usage")}
|
|
67
|
+
$ svelte-bundle -i App.svelte
|
|
68
|
+
|
|
69
|
+
${chalk.gray("# With output directory")}
|
|
70
|
+
$ svelte-bundle -i App.svelte -o dist
|
|
71
|
+
|
|
72
|
+
${chalk.gray("# With Tailwind CSS")}
|
|
73
|
+
$ svelte-bundle -i App.svelte --tw
|
|
74
|
+
|
|
75
|
+
${chalk.gray("# With custom Tailwind config")}
|
|
76
|
+
$ svelte-bundle -i App.svelte --tw --tw-config tailwind.config.js
|
|
77
|
+
|
|
78
|
+
${chalk.gray("# With verbose output")}
|
|
79
|
+
$ svelte-bundle -i App.svelte -v
|
|
80
|
+
`,
|
|
81
|
+
);
|
|
46
82
|
|
|
47
83
|
program.parse();
|
|
48
|
-
|
|
49
84
|
const options = program.opts();
|
|
50
85
|
|
|
86
|
+
/**
|
|
87
|
+
* Loads a Tailwind configuration file
|
|
88
|
+
*/
|
|
51
89
|
async function loadTailwindConfig(configPath) {
|
|
52
90
|
const fullPath = path.resolve(configPath);
|
|
53
91
|
try {
|
|
54
92
|
const { default: config } = await import(fullPath);
|
|
55
93
|
return config;
|
|
56
94
|
} catch (error) {
|
|
57
|
-
|
|
58
|
-
|
|
95
|
+
throw new Error(`Failed to load Tailwind config: ${error.message}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Validates the input file exists and is a .svelte file
|
|
101
|
+
*/
|
|
102
|
+
async function validateInput(inputPath) {
|
|
103
|
+
if (path.extname(inputPath) !== ".svelte") {
|
|
104
|
+
throw new Error("Input file must have a .svelte extension");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
await fs.access(inputPath);
|
|
109
|
+
} catch {
|
|
110
|
+
throw new Error(`Input file not found: ${inputPath}`);
|
|
59
111
|
}
|
|
60
112
|
}
|
|
61
113
|
|
|
114
|
+
/**
|
|
115
|
+
* Checks if output file exists and prompts for overwrite if needed
|
|
116
|
+
*/
|
|
117
|
+
async function checkOutputExists(outputPath, force) {
|
|
118
|
+
const exists = await fs
|
|
119
|
+
.access(outputPath)
|
|
120
|
+
.then(() => true)
|
|
121
|
+
.catch(() => false);
|
|
122
|
+
|
|
123
|
+
if (exists && !force) {
|
|
124
|
+
const response = await question(
|
|
125
|
+
chalk.yellow(
|
|
126
|
+
`\n⚠ File ${chalk.white(path.basename(outputPath))} already exists. Overwrite? (y/N): `,
|
|
127
|
+
),
|
|
128
|
+
);
|
|
129
|
+
if (response.toLowerCase() !== "y") {
|
|
130
|
+
console.log(chalk.gray("\nOperation cancelled."));
|
|
131
|
+
process.exit(0);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Displays build configuration summary
|
|
138
|
+
*/
|
|
139
|
+
function displayBuildInfo(inputPath, outputDir, useTailwind) {
|
|
140
|
+
console.log(chalk.cyan("Build Configuration:"));
|
|
141
|
+
console.log(chalk.gray("┌─────────────────────────────────────────"));
|
|
142
|
+
console.log(
|
|
143
|
+
chalk.gray("│ Input: ") +
|
|
144
|
+
chalk.white(path.relative(process.cwd(), inputPath)),
|
|
145
|
+
);
|
|
146
|
+
console.log(
|
|
147
|
+
chalk.gray("│ Output: ") +
|
|
148
|
+
chalk.white(path.relative(process.cwd(), outputDir)),
|
|
149
|
+
);
|
|
150
|
+
console.log(
|
|
151
|
+
chalk.gray("│ Tailwind: ") +
|
|
152
|
+
(useTailwind ? chalk.green("✓ Enabled") : chalk.gray("✗ Disabled")),
|
|
153
|
+
);
|
|
154
|
+
console.log(chalk.gray("└─────────────────────────────────────────\n"));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Main validation and build process
|
|
159
|
+
*/
|
|
62
160
|
async function validateAndProcess() {
|
|
63
161
|
try {
|
|
64
|
-
|
|
162
|
+
displayBanner();
|
|
163
|
+
|
|
164
|
+
// Resolve paths
|
|
65
165
|
const inputPath = path.resolve(options.input);
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
process.exit(1);
|
|
71
|
-
}
|
|
166
|
+
const outputDir = options.output
|
|
167
|
+
? path.resolve(options.output)
|
|
168
|
+
: process.cwd();
|
|
169
|
+
const outputPath = path.join(outputDir, "output.html");
|
|
72
170
|
|
|
73
|
-
//
|
|
74
|
-
|
|
75
|
-
await fs.access(inputPath);
|
|
76
|
-
} catch {
|
|
77
|
-
console.error(chalk.red(`Error: Input file ${inputPath} does not exist`));
|
|
78
|
-
process.exit(1);
|
|
79
|
-
}
|
|
171
|
+
// Validate input file
|
|
172
|
+
await validateInput(inputPath);
|
|
80
173
|
|
|
81
174
|
// Handle Tailwind configuration
|
|
82
175
|
let tailwindConfig = null;
|
|
83
|
-
if (options.tw) {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
} catch (error) {
|
|
89
|
-
console.error(chalk.red(`Error loading Tailwind config: ${error.message}`));
|
|
90
|
-
process.exit(1);
|
|
91
|
-
}
|
|
92
|
-
} else {
|
|
93
|
-
console.log(chalk.blue('Using default Tailwind configuration'));
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Validate Tailwind config usage
|
|
98
|
-
if (options.twConfig && !options.tw) {
|
|
99
|
-
console.error(chalk.yellow('Warning: Tailwind config provided but Tailwind is not enabled. Use --tw to enable Tailwind.'));
|
|
100
|
-
process.exit(1);
|
|
176
|
+
if (options.tw && options.twConfig) {
|
|
177
|
+
tailwindConfig = await loadTailwindConfig(options.twConfig);
|
|
178
|
+
console.log(chalk.green("✓ Loaded custom Tailwind configuration\n"));
|
|
179
|
+
} else if (options.twConfig && !options.tw) {
|
|
180
|
+
throw new Error("Tailwind config provided but --tw flag not set");
|
|
101
181
|
}
|
|
102
182
|
|
|
103
|
-
// Determine output directory
|
|
104
|
-
let outputDir = process.cwd();
|
|
105
|
-
if (options.output) {
|
|
106
|
-
outputDir = path.resolve(options.output);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const outputPath = path.join(outputDir, 'output.html');
|
|
110
|
-
|
|
111
183
|
// Check if output file exists
|
|
112
|
-
|
|
113
|
-
if (!options.force) {
|
|
114
|
-
const shouldProceed = await question(
|
|
115
|
-
chalk.yellow(`File ${outputPath} already exists. Overwrite? (y/N): `)
|
|
116
|
-
);
|
|
117
|
-
if (shouldProceed.toLowerCase() !== 'y') {
|
|
118
|
-
console.log(chalk.yellow('Operation cancelled.'));
|
|
119
|
-
process.exit(0);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
184
|
+
await checkOutputExists(outputPath, options.force);
|
|
123
185
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
186
|
+
// Display build configuration
|
|
187
|
+
displayBuildInfo(inputPath, outputDir, options.tw);
|
|
188
|
+
|
|
189
|
+
// Start build process
|
|
190
|
+
console.log(chalk.cyan("⚡ Building..."));
|
|
191
|
+
const startTime = Date.now();
|
|
130
192
|
|
|
131
193
|
const buildOptions = {
|
|
132
194
|
useTailwind: options.tw || false,
|
|
133
|
-
tailwindConfig: tailwindConfig
|
|
195
|
+
tailwindConfig: tailwindConfig,
|
|
196
|
+
verbose: options.verbose || false,
|
|
134
197
|
};
|
|
135
198
|
|
|
136
199
|
await buildStaticFile(inputPath, outputDir, buildOptions);
|
|
137
200
|
|
|
138
|
-
|
|
139
|
-
|
|
201
|
+
const duration = ((Date.now() - startTime) / 1000).toFixed(2);
|
|
202
|
+
|
|
203
|
+
// Success message
|
|
204
|
+
console.log(chalk.green("\n✓ Build completed successfully!"));
|
|
205
|
+
console.log(chalk.gray(` Time: ${duration}s`));
|
|
206
|
+
console.log(
|
|
207
|
+
chalk.gray(
|
|
208
|
+
` Output: ${chalk.white(path.relative(process.cwd(), outputPath))}\n`,
|
|
209
|
+
),
|
|
210
|
+
);
|
|
140
211
|
} catch (error) {
|
|
141
|
-
console.
|
|
142
|
-
console.error(chalk.red(error.message));
|
|
143
|
-
|
|
212
|
+
console.log(chalk.red("\n✗ Build failed\n"));
|
|
213
|
+
console.error(chalk.red("Error: ") + chalk.white(error.message));
|
|
214
|
+
|
|
215
|
+
if (process.env.DEBUG) {
|
|
216
|
+
console.error(chalk.gray("\nStack trace:"));
|
|
144
217
|
console.error(chalk.gray(error.stack));
|
|
145
218
|
}
|
|
219
|
+
|
|
146
220
|
process.exit(1);
|
|
147
221
|
} finally {
|
|
148
222
|
rl.close();
|
|
149
223
|
}
|
|
150
224
|
}
|
|
151
225
|
|
|
152
|
-
validateAndProcess();
|
|
226
|
+
validateAndProcess();
|
package/package.json
CHANGED
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "svelte-bundle",
|
|
3
|
-
"version": "0.0
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI tool to easily bundle a .svelte file into a single .html file",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"title": "svelte-bundle",
|
|
7
|
+
"url": "https://github.com/uhteddy/svelte-bundle",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/uhteddy/svelte-bundle.git"
|
|
11
|
+
},
|
|
12
|
+
"npm": "svelte-bundle",
|
|
13
|
+
"categories": [
|
|
14
|
+
"build-plugins"
|
|
15
|
+
],
|
|
6
16
|
"bin": {
|
|
7
17
|
"svelte-bundle": "cli.js"
|
|
8
18
|
},
|
|
@@ -21,10 +31,6 @@
|
|
|
21
31
|
"access": "public",
|
|
22
32
|
"registry": "https://registry.npmjs.org/"
|
|
23
33
|
},
|
|
24
|
-
"repository": {
|
|
25
|
-
"type": "git",
|
|
26
|
-
"url": "git+https://github.com/uhteddy/svelte-bundle.git"
|
|
27
|
-
},
|
|
28
34
|
"scripts": {
|
|
29
35
|
"prepublishOnly": "chmod +x cli.js",
|
|
30
36
|
"test": "vitest run",
|
|
@@ -45,12 +51,12 @@
|
|
|
45
51
|
"rollup-plugin-css-only": "^4.3.0",
|
|
46
52
|
"rollup-plugin-postcss": "^4.0.2",
|
|
47
53
|
"rollup-plugin-svelte": "^7.1.4",
|
|
48
|
-
"svelte": "^
|
|
54
|
+
"svelte": "^5.50.0",
|
|
49
55
|
"tailwindcss": "^3.4.14"
|
|
50
56
|
},
|
|
51
57
|
"devDependencies": {
|
|
52
|
-
"@vitest/coverage-v8": "^
|
|
53
|
-
"vitest": "^
|
|
58
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
59
|
+
"vitest": "^4.0.18"
|
|
54
60
|
},
|
|
55
61
|
"bugs": {
|
|
56
62
|
"url": "https://github.com/uhteddy/svelte-bundle/issues"
|