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.
Files changed (4) hide show
  1. package/ReadMe.md +3 -1
  2. package/bundle.js +308 -164
  3. package/cli.js +166 -92
  4. 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
+ ![svelte-bundle](https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcwfyh1w8r8g4f2ap1hff.png)
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 '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';
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
- export async function buildStaticFile(svelteFilePath, outputDir, options = {}) {
22
- const { useTailwind = false, tailwindConfig = null } = options;
21
+ /**
22
+ * Creates PostCSS plugins array based on Tailwind configuration
23
+ */
24
+ function createPostCSSPlugins(useTailwind, tailwindConfig, svelteFilePath) {
25
+ const plugins = [];
23
26
 
24
- try {
25
- // Ensure output directory exists
26
- await fs.mkdir(outputDir, { recursive: true });
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
- let cssText = '';
29
-
30
- // Setup PostCSS plugins based on whether Tailwind is enabled
31
- const postcssPlugins = useTailwind
32
- ? [
33
- tailwindcss(tailwindConfig || {
34
- content: [svelteFilePath],
35
- theme: { extend: {} },
36
- plugins: [],
37
- }),
38
- autoprefixer(),
39
- cssnano({
40
- preset: ['default', {
41
- discardComments: {
42
- removeAll: true,
43
- },
44
- }],
45
- })
46
- ]
47
- : [
48
- autoprefixer(),
49
- cssnano({
50
- preset: ['default', {
51
- discardComments: {
52
- removeAll: true,
53
- },
54
- }],
55
- })
56
- ];
57
-
58
- // Process global styles
59
- let globalCssText = '';
60
- if (useTailwind) {
61
- const tailwindCss = `
62
- @tailwind base;
63
- @tailwind components;
64
- @tailwind utilities;
65
- `;
66
-
67
- const processedCss = await postcss(postcssPlugins)
68
- .process(tailwindCss, { from: undefined });
69
- globalCssText = processedCss.css;
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
- // Get the absolute path to svelte/internal
73
- const svelteInternalPath = require.resolve('svelte/internal');
74
-
75
- // Create temporary SSR bundle
76
- const ssrBundle = await rollup({
77
- input: svelteFilePath,
78
- plugins: [
79
- svelte({
80
- compilerOptions: {
81
- generate: 'ssr',
82
- hydratable: true,
83
- css: false
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
- resolve({
101
- browser: true,
102
- dedupe: ['svelte'],
103
- modulePaths: [path.join(__dirname, 'node_modules')],
104
- rootDir: __dirname
105
- }),
106
- commonjs()
107
- ],
108
- external: ['svelte/internal']
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
- // Create a temporary directory in the CLI package directory
112
- const tempDir = path.join(__dirname, '.temp');
113
- await fs.mkdir(tempDir, { recursive: true });
114
- const tempSSRFile = path.join(tempDir, 'ssr-bundle.js');
115
-
116
- // Generate SSR bundle as ESM
117
- await ssrBundle.write({
118
- file: tempSSRFile,
119
- format: 'es',
120
- exports: 'default',
121
- paths: {
122
- 'svelte/internal': svelteInternalPath
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
- // Import the SSR bundle using dynamic import
127
- const { default: App } = await import(/* @vite-ignore */`file://${tempSSRFile}`);
128
- const { html: initialHtml } = App.render();
129
-
130
- // Clean up temp files
131
- await fs.rm(tempDir, { recursive: true, force: true });
132
-
133
- // Build client-side bundle
134
- const clientBundle = await rollup({
135
- input: svelteFilePath,
136
- plugins: [
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
- css({
153
- output: function(styles) {
154
- cssText = styles;
155
- }
156
- }),
157
- resolve({
158
- browser: true,
159
- dedupe: ['svelte'],
160
- modulePaths: [path.join(__dirname, 'node_modules')],
161
- rootDir: __dirname
162
- }),
163
- commonjs(),
164
- terser()
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
- const { output: [{ code: clientCode }] } = await clientBundle.generate({
169
- format: 'iife',
170
- name: 'App',
171
- globals: {
172
- svelte: 'Svelte'
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
- // Create the final HTML
177
- const finalHtml = `<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Static Svelte App</title><style>${globalCssText}${cssText}</style></head><body><div id="app">${initialHtml}</div><script>${clientCode}const app=new App({target:document.getElementById("app"),hydrate:!0});</script></body></html>`;
277
+ // Suppress console warnings unless verbose mode is enabled
278
+ const originalConsoleWarn = console.warn;
178
279
 
179
- // Write the output file
180
- const outputPath = path.join(outputDir, 'output.html');
181
- await fs.writeFile(outputPath, finalHtml, 'utf-8');
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('Build error:', 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 '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
-
10
- // Get package.json data
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) => new Promise((resolve) => rl.question(query, resolve));
24
+ const question = (query) =>
25
+ new Promise((resolve) => rl.question(query, resolve));
28
26
 
29
- // Add check for node version
27
+ // Validate Node.js version
30
28
  const nodeVersion = process.versions.node;
31
- const [major] = nodeVersion.split('.').map(Number);
29
+ const [major] = nodeVersion.split(".").map(Number);
32
30
  if (major < 18) {
33
- console.error(chalk.red(`Error: Node.js version 18 or higher is required. Current version: ${nodeVersion}`));
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('svelte-bundle')
39
- .description(packageJson.description)
50
+ .name("svelte-bundle")
51
+ .description(chalk.gray(packageJson.description))
40
52
  .version(packageJson.version)
41
- .requiredOption('-i, --input <path>', 'Input Svelte file')
42
- .option('-o, --output <path>', 'Output directory (defaults to current directory)')
43
- .option('--tw', 'Enable Tailwind CSS processing')
44
- .option('--tw-config <path>', 'Path to custom Tailwind config file')
45
- .option('-f, --force', 'Force overwrite without asking');
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
- console.error(chalk.red(`Error loading Tailwind config: ${error.message}`));
58
- process.exit(1);
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
- // Validate input file
162
+ displayBanner();
163
+
164
+ // Resolve paths
65
165
  const inputPath = path.resolve(options.input);
66
- const inputExt = path.extname(inputPath);
67
-
68
- if (inputExt !== '.svelte') {
69
- console.error(chalk.red('Error: Input file must be a .svelte file'));
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
- // Check if input file exists
74
- try {
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
- if (options.twConfig) {
85
- try {
86
- tailwindConfig = await loadTailwindConfig(options.twConfig);
87
- console.log(chalk.blue('Using custom Tailwind configuration'));
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
- if (await fs.access(outputPath).then(() => true).catch(() => false)) {
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
- console.log(chalk.blue('Starting build process...'));
125
- console.log(chalk.gray(`Input: ${inputPath}`));
126
- console.log(chalk.gray(`Output directory: ${outputDir}`));
127
- if (options.tw) {
128
- console.log(chalk.gray('Tailwind CSS enabled'));
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
- console.log(chalk.green('\n✨ Build completed successfully!'));
139
- console.log(chalk.gray(`Output file: ${outputPath}`));
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.error(chalk.red('\nBuild failed:'));
142
- console.error(chalk.red(error.message));
143
- if (error.stack) {
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.23",
4
- "description": "Bundle Svelte files into static HTML files",
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": "^3.58.0",
54
+ "svelte": "^5.50.0",
49
55
  "tailwindcss": "^3.4.14"
50
56
  },
51
57
  "devDependencies": {
52
- "@vitest/coverage-v8": "^2.1.3",
53
- "vitest": "^2.1.3"
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"