svelte-bundle 0.0.1 → 0.0.21

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 +14 -17
  2. package/bundle.js +74 -23
  3. package/cli.js +59 -26
  4. package/package.json +27 -4
package/ReadMe.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## Usage
2
+ This tool can be used with `npx`:
3
+ ```bash
4
+ npx svelte-bundle -i <input-dir> -o <output-dir>
5
+ ```
6
+
1
7
  # Svelte Bundle CLI
2
8
 
3
9
  **Svelte Bundle CLI** is a simple command-line tool that allows you to bundle a Svelte application into a single `.html` file using Vite and SSR (Server-Side Rendering). The goal of this tool is to make it easy to bundle Svelte apps for deployment, particularly for cases where everything needs to be contained in a single file.
@@ -19,20 +25,11 @@ So, I searched for tools around that could be of assistance. I found [figsvelte]
19
25
 
20
26
  I noticed through a lot of google searches I wasn't the only one looking for a solution like this, yet, I was unable to find one that addressed everything I was looking for. So, for this reason I have decided to build svelte-bundle to take care of this in a much more simplistic and CLI way.
21
27
 
22
- ## Features (In Progress)
23
- - [ ] Bundles Svelte applications using Vite and SSR.
24
- - [ ] Outputs a single `.html` file ready for deployment.
25
- - [ ] CLI arguments for specifying input and output directories.
26
-
27
- ## Roadmap
28
- - [ ] Add options for SSR hydration
29
- - [ ] Handle CSS and assets within the bundled file.
30
- - [ ] Implement error handling and more robust validation.
31
- - [ ] Add support for environment-specific builds (development/production).
32
- - [ ] Documentation and guides on using the tool with different Svelte apps.
33
- - [ ] Tests and CI integration.
34
-
35
- ## Installation (Planned)
36
- Once the tool is published on npm, it will be available via `npx`:
37
- ```bash
38
- npx svelte-bundle -i <input-dir> -o <output-dir>
28
+ ## Features
29
+ - [x] Bundles Svelte applications
30
+ - [x] Outputs a single `.html` file ready for deployment.
31
+ - [x] CLI arguments for specifying input and output directories.
32
+ - [x] Tests and CI integration.
33
+ - [x] Handle CSS and assets within the bundled file.
34
+ - [x] Add tailwindcss support, **this is in beta**.
35
+ - [x] Implement error handling and more robust validation.
package/bundle.js CHANGED
@@ -1,17 +1,68 @@
1
+ // bundle.js
1
2
  import fs from 'fs/promises';
2
3
  import path from 'path';
3
4
  import { rollup } from 'rollup';
4
5
  import svelte from 'rollup-plugin-svelte';
5
6
  import resolve from '@rollup/plugin-node-resolve';
6
7
  import commonjs from '@rollup/plugin-commonjs';
7
- import { compile } from 'svelte/compiler';
8
8
  import css from 'rollup-plugin-css-only';
9
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
+
15
+ export async function buildStaticFile(svelteFilePath, outputDir, options = {}) {
16
+ const { useTailwind = false, tailwindConfig = null } = options;
10
17
 
11
- export async function buildStaticFile(svelteFilePath, outputDir) {
12
18
  try {
19
+ // Ensure output directory exists
20
+ await fs.mkdir(outputDir, { recursive: true });
21
+
13
22
  let cssText = '';
14
23
 
24
+ // Setup PostCSS plugins based on whether Tailwind is enabled
25
+ const postcssPlugins = useTailwind
26
+ ? [
27
+ tailwindcss(tailwindConfig || {
28
+ content: [svelteFilePath],
29
+ theme: { extend: {} },
30
+ plugins: [],
31
+ }),
32
+ autoprefixer(),
33
+ cssnano({
34
+ preset: ['default', {
35
+ discardComments: {
36
+ removeAll: true,
37
+ },
38
+ }],
39
+ })
40
+ ]
41
+ : [
42
+ autoprefixer(),
43
+ cssnano({
44
+ preset: ['default', {
45
+ discardComments: {
46
+ removeAll: true,
47
+ },
48
+ }],
49
+ })
50
+ ];
51
+
52
+ // Process global styles
53
+ let globalCssText = '';
54
+ if (useTailwind) {
55
+ const tailwindCss = `
56
+ @tailwind base;
57
+ @tailwind components;
58
+ @tailwind utilities;
59
+ `;
60
+
61
+ const processedCss = await postcss(postcssPlugins)
62
+ .process(tailwindCss, { from: undefined });
63
+ globalCssText = processedCss.css;
64
+ }
65
+
15
66
  // Create temporary SSR bundle
16
67
  const ssrBundle = await rollup({
17
68
  input: svelteFilePath,
@@ -22,7 +73,15 @@ export async function buildStaticFile(svelteFilePath, outputDir) {
22
73
  hydratable: true,
23
74
  css: false
24
75
  },
25
- emitCss: true
76
+ emitCss: true,
77
+ preprocess: useTailwind ? {
78
+ style: async ({ content }) => {
79
+ if (!content) return { code: '' };
80
+ const result = await postcss(postcssPlugins)
81
+ .process(content, { from: undefined });
82
+ return { code: result.css };
83
+ }
84
+ } : undefined
26
85
  }),
27
86
  css({
28
87
  output: function(styles) {
@@ -54,7 +113,7 @@ export async function buildStaticFile(svelteFilePath, outputDir) {
54
113
  const { default: App } = await import(tempSSRFile);
55
114
  const { html: initialHtml } = App.render();
56
115
 
57
- // Clean up temp file
116
+ // Clean up temp files
58
117
  await fs.rm(tempDir, { recursive: true, force: true });
59
118
 
60
119
  // Build client-side bundle
@@ -66,7 +125,15 @@ export async function buildStaticFile(svelteFilePath, outputDir) {
66
125
  hydratable: true,
67
126
  css: false
68
127
  },
69
- emitCss: true
128
+ emitCss: true,
129
+ preprocess: useTailwind ? {
130
+ style: async ({ content }) => {
131
+ if (!content) return { code: '' };
132
+ const result = await postcss(postcssPlugins)
133
+ .process(content, { from: undefined });
134
+ return { code: result.css };
135
+ }
136
+ } : undefined
70
137
  }),
71
138
  css({
72
139
  output: function(styles) {
@@ -91,29 +158,13 @@ export async function buildStaticFile(svelteFilePath, outputDir) {
91
158
  });
92
159
 
93
160
  // Create the final HTML
94
- const finalHtml = `<!DOCTYPE html>
95
- <html lang="en">
96
- <head>
97
- <meta charset="UTF-8">
98
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
99
- <title>Static Svelte App</title>
100
- <style>
101
- ${cssText}
102
- </style>
103
- </head>
104
- <body>
105
- <div id="app">${initialHtml}</div>
106
- <script>
107
- ${clientCode}
108
- const app = new App({target: document.getElementById('app'), hydrate: true});
109
- </script>
110
- </body>
111
- </html>`;
161
+ 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 src="https://unpkg.com/svelte@3.58.0/internal/index.js"></script><script>${clientCode}const app=new App({target:document.getElementById("app"),hydrate:!0});</script></body></html>`;
112
162
 
113
163
  // Write the output file
114
164
  const outputPath = path.join(outputDir, 'output.html');
115
165
  await fs.writeFile(outputPath, finalHtml, 'utf-8');
116
166
  } catch (error) {
167
+ console.error('Build error:', error);
117
168
  throw error;
118
169
  }
119
170
  }
package/cli.js CHANGED
@@ -7,46 +7,50 @@ import { createInterface } from 'readline';
7
7
  import { buildStaticFile } from './bundle.js';
8
8
  import { fileURLToPath } from 'url';
9
9
 
10
+ // Get package.json data
11
+ import { readFile } from 'fs/promises';
10
12
  const __filename = fileURLToPath(import.meta.url);
11
13
  const __dirname = path.dirname(__filename);
12
14
 
13
- // Create readline interface for prompts
15
+ // Read package.json
16
+ const packageJson = JSON.parse(
17
+ await readFile(
18
+ new URL('./package.json', import.meta.url)
19
+ )
20
+ );
21
+
14
22
  const rl = createInterface({
15
23
  input: process.stdin,
16
24
  output: process.stdout
17
25
  });
18
26
 
19
- // Promisify readline question
20
27
  const question = (query) => new Promise((resolve) => rl.question(query, resolve));
21
28
 
22
29
  program
23
30
  .name('svelte-bundle')
24
- .description('Bundle a Svelte file into a standalone .html file')
25
- .version('0.0.1')
31
+ .description(packageJson.description)
32
+ .version(packageJson.version)
26
33
  .requiredOption('-i, --input <path>', 'Input Svelte file')
27
34
  .option('-o, --output <path>', 'Output directory (defaults to current directory)')
35
+ .option('--tw', 'Enable Tailwind CSS processing')
36
+ .option('--tw-config <path>', 'Path to custom Tailwind config file')
28
37
  .option('-f, --force', 'Force overwrite without asking');
29
38
 
30
39
  program.parse();
31
40
 
32
41
  const options = program.opts();
33
42
 
34
- async function checkFileExists(filepath) {
43
+ async function loadTailwindConfig(configPath) {
44
+ const fullPath = path.resolve(configPath);
35
45
  try {
36
- await fs.access(filepath);
37
- return true;
38
- } catch {
39
- return false;
46
+ const { default: config } = await import(fullPath);
47
+ return config;
48
+ } catch (error) {
49
+ console.error(chalk.red(`Error loading Tailwind config: ${error.message}`));
50
+ process.exit(1);
40
51
  }
41
52
  }
42
53
 
43
- async function shouldOverwrite(filepath) {
44
- const answer = await question(
45
- chalk.yellow(`File ${filepath} already exists. Overwrite? (y/N): `)
46
- );
47
- return answer.toLowerCase() === 'y';
48
- }
49
-
50
54
  async function validateAndProcess() {
51
55
  try {
52
56
  // Validate input file
@@ -66,21 +70,43 @@ async function validateAndProcess() {
66
70
  process.exit(1);
67
71
  }
68
72
 
69
- // Determine output directory and file path
73
+ // Handle Tailwind configuration
74
+ let tailwindConfig = null;
75
+ if (options.tw) {
76
+ if (options.twConfig) {
77
+ try {
78
+ tailwindConfig = await loadTailwindConfig(options.twConfig);
79
+ console.log(chalk.blue('Using custom Tailwind configuration'));
80
+ } catch (error) {
81
+ console.error(chalk.red(`Error loading Tailwind config: ${error.message}`));
82
+ process.exit(1);
83
+ }
84
+ } else {
85
+ console.log(chalk.blue('Using default Tailwind configuration'));
86
+ }
87
+ }
88
+
89
+ // Validate Tailwind config usage
90
+ if (options.twConfig && !options.tw) {
91
+ console.error(chalk.yellow('Warning: Tailwind config provided but Tailwind is not enabled. Use --tw to enable Tailwind.'));
92
+ process.exit(1);
93
+ }
94
+
95
+ // Determine output directory
70
96
  let outputDir = process.cwd();
71
97
  if (options.output) {
72
98
  outputDir = path.resolve(options.output);
73
- // Create output directory if it doesn't exist
74
- await fs.mkdir(outputDir, { recursive: true });
75
99
  }
76
100
 
77
101
  const outputPath = path.join(outputDir, 'output.html');
78
102
 
79
- // Check if output file exists and handle overwriting
80
- if (await checkFileExists(outputPath)) {
103
+ // Check if output file exists
104
+ if (await fs.access(outputPath).then(() => true).catch(() => false)) {
81
105
  if (!options.force) {
82
- const shouldProceed = await shouldOverwrite(outputPath);
83
- if (!shouldProceed) {
106
+ const shouldProceed = await question(
107
+ chalk.yellow(`File ${outputPath} already exists. Overwrite? (y/N): `)
108
+ );
109
+ if (shouldProceed.toLowerCase() !== 'y') {
84
110
  console.log(chalk.yellow('Operation cancelled.'));
85
111
  process.exit(0);
86
112
  }
@@ -89,10 +115,17 @@ async function validateAndProcess() {
89
115
 
90
116
  console.log(chalk.blue('Starting build process...'));
91
117
  console.log(chalk.gray(`Input: ${inputPath}`));
92
- console.log(chalk.gray(`Output: ${outputPath}`));
118
+ console.log(chalk.gray(`Output directory: ${outputDir}`));
119
+ if (options.tw) {
120
+ console.log(chalk.gray('Tailwind CSS enabled'));
121
+ }
122
+
123
+ const buildOptions = {
124
+ useTailwind: options.tw || false,
125
+ tailwindConfig: tailwindConfig
126
+ };
93
127
 
94
- // Process the file
95
- await buildStaticFile(inputPath, outputDir);
128
+ await buildStaticFile(inputPath, outputDir, buildOptions);
96
129
 
97
130
  console.log(chalk.green('\n✨ Build completed successfully!'));
98
131
  console.log(chalk.gray(`Output file: ${outputPath}`));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelte-bundle",
3
- "version": "0.0.1",
3
+ "version": "0.0.21",
4
4
  "description": "Bundle Svelte files into static HTML files",
5
5
  "type": "module",
6
6
  "bin": {
@@ -18,10 +18,18 @@
18
18
  "ssr"
19
19
  ],
20
20
  "publishConfig": {
21
- "access": "public"
21
+ "access": "public",
22
+ "registry": "https://registry.npmjs.org/"
23
+ },
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git+https://github.com/uhteddy/svelte-bundle.git"
22
27
  },
23
28
  "scripts": {
24
- "prepublishOnly": "chmod +x cli.js"
29
+ "prepublishOnly": "chmod +x cli.js",
30
+ "test": "vitest run",
31
+ "test:watch": "vitest",
32
+ "test:coverage": "vitest run --coverage"
25
33
  },
26
34
  "author": "Teddy Hartling",
27
35
  "license": "MIT",
@@ -29,11 +37,26 @@
29
37
  "@rollup/plugin-commonjs": "^24.0.1",
30
38
  "@rollup/plugin-node-resolve": "^15.0.2",
31
39
  "@rollup/plugin-terser": "^0.4.0",
40
+ "autoprefixer": "^10.4.20",
32
41
  "chalk": "^5.3.0",
33
42
  "commander": "^11.0.0",
43
+ "cssnano": "^7.0.6",
34
44
  "rollup": "^3.20.0",
35
45
  "rollup-plugin-css-only": "^4.3.0",
46
+ "rollup-plugin-postcss": "^4.0.2",
36
47
  "rollup-plugin-svelte": "^7.1.4",
37
- "svelte": "^3.58.0"
48
+ "svelte": "^3.58.0",
49
+ "tailwindcss": "^3.4.14"
50
+ },
51
+ "devDependencies": {
52
+ "@vitest/coverage-v8": "^2.1.3",
53
+ "vitest": "^2.1.3"
54
+ },
55
+ "bugs": {
56
+ "url": "https://github.com/uhteddy/svelte-bundle/issues"
57
+ },
58
+ "homepage": "https://github.com/uhteddy/svelte-bundle#readme",
59
+ "engines": {
60
+ "node": ">=18"
38
61
  }
39
62
  }