svelte-bundle 0.0.12 → 0.0.22

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 +18 -20
  2. package/bundle.js +120 -33
  3. package/cli.js +49 -25
  4. package/package.json +12 -3
package/ReadMe.md CHANGED
@@ -1,3 +1,12 @@
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
+
3
+ ## Usage
4
+ This tool can be used with `npx`:
5
+ ```bash
6
+ npx svelte-bundle -i <input-dir> -o <output-dir>
7
+ ```
8
+ ### Full Documentation: [https://github.com/uhteddy/svelte-bundle/wiki](https://github.com/uhteddy/svelte-bundle/wiki)
9
+
1
10
  # Svelte Bundle CLI
2
11
 
3
12
  **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.
@@ -6,9 +15,7 @@ Just note, the purpose of this tool is **NOT** to bundle an entire Svelte app, i
6
15
 
7
16
  Utilizing this you will be able to develop a page with the joy of svelte and be able to directly get a single `.html` file that you can utilize getting full use-case of svelte.
8
17
 
9
- ⚠️ **Note**: This tool is **NOT** made to function with SvelteKit, this takes a single `.svelte` file as input and utilizes vite to compile it into a single `.html` file.
10
-
11
- ⚠️ **Note**: This tool is currently in early development and is not fully complete. The roadmap and additional features will be added over time. There is currently **NO** testing on this, meaning there is no guarentee it will work for most usecases.
18
+ ⚠️ **Note**: This tool is **NOT** made to function with SvelteKit natively, this takes a single `.svelte` file as input and will output a single `.html` file.
12
19
 
13
20
  ## Inspiration
14
21
  This tool was inspired by the need I had when it came to updating the CMS for a company I worked for. They were looking for more custom content on their website which used an outdated CMS. Pages were only able to include HTML, CSS, and JS.
@@ -19,20 +26,11 @@ So, I searched for tools around that could be of assistance. I found [figsvelte]
19
26
 
20
27
  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
28
 
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>
29
+ ## Features
30
+ - [x] Bundles Svelte applications
31
+ - [x] Outputs a single `.html` file ready for deployment.
32
+ - [x] CLI arguments for specifying input and output directories.
33
+ - [x] Tests and CI integration.
34
+ - [x] Handle CSS and assets within the bundled file.
35
+ - [x] Add tailwindcss support, **this is in beta**.
36
+ - [x] Implement error handling and more robust validation.
package/bundle.js CHANGED
@@ -1,4 +1,3 @@
1
- // bundle.js
2
1
  import fs from 'fs/promises';
3
2
  import path from 'path';
4
3
  import { rollup } from 'rollup';
@@ -7,11 +6,76 @@ import resolve from '@rollup/plugin-node-resolve';
7
6
  import commonjs from '@rollup/plugin-commonjs';
8
7
  import css from 'rollup-plugin-css-only';
9
8
  import terser from '@rollup/plugin-terser';
9
+ import postcss from 'postcss';
10
+ import tailwindcss from 'tailwindcss';
11
+ import autoprefixer from 'autoprefixer';
12
+ import cssnano from 'cssnano';
13
+ import { createRequire } from 'module';
14
+ import { fileURLToPath } from 'url';
15
+
16
+ const require = createRequire(import.meta.url);
17
+ const __filename = fileURLToPath(import.meta.url);
18
+ const __dirname = path.dirname(__filename);
19
+
20
+ // Function to find the CLI's node_modules directory
21
+ const findCliNodeModules = () => {
22
+ return path.resolve(__dirname, 'node_modules');
23
+ };
24
+
25
+ export async function buildStaticFile(svelteFilePath, outputDir, options = {}) {
26
+ const { useTailwind = false, tailwindConfig = null } = options;
10
27
 
11
- export async function buildStaticFile(svelteFilePath, outputDir) {
12
28
  try {
29
+ // Ensure output directory exists
30
+ await fs.mkdir(outputDir, { recursive: true });
31
+
32
+ // Get CLI's node_modules path
33
+ const cliNodeModules = findCliNodeModules();
34
+
13
35
  let cssText = '';
14
36
 
37
+ // Setup PostCSS plugins based on whether Tailwind is enabled
38
+ const postcssPlugins = useTailwind
39
+ ? [
40
+ tailwindcss(tailwindConfig || {
41
+ content: [svelteFilePath],
42
+ theme: { extend: {} },
43
+ plugins: [],
44
+ }),
45
+ autoprefixer(),
46
+ cssnano({
47
+ preset: ['default', {
48
+ discardComments: {
49
+ removeAll: true,
50
+ },
51
+ }],
52
+ })
53
+ ]
54
+ : [
55
+ autoprefixer(),
56
+ cssnano({
57
+ preset: ['default', {
58
+ discardComments: {
59
+ removeAll: true,
60
+ },
61
+ }],
62
+ })
63
+ ];
64
+
65
+ // Process global styles
66
+ let globalCssText = '';
67
+ if (useTailwind) {
68
+ const tailwindCss = `
69
+ @tailwind base;
70
+ @tailwind components;
71
+ @tailwind utilities;
72
+ `;
73
+
74
+ const processedCss = await postcss(postcssPlugins)
75
+ .process(tailwindCss, { from: undefined });
76
+ globalCssText = processedCss.css;
77
+ }
78
+
15
79
  // Create temporary SSR bundle
16
80
  const ssrBundle = await rollup({
17
81
  input: svelteFilePath,
@@ -22,7 +86,15 @@ export async function buildStaticFile(svelteFilePath, outputDir) {
22
86
  hydratable: true,
23
87
  css: false
24
88
  },
25
- emitCss: true
89
+ emitCss: true,
90
+ preprocess: useTailwind ? {
91
+ style: async ({ content }) => {
92
+ if (!content) return { code: '' };
93
+ const result = await postcss(postcssPlugins)
94
+ .process(content, { from: undefined });
95
+ return { code: result.css };
96
+ }
97
+ } : undefined
26
98
  }),
27
99
  css({
28
100
  output: function(styles) {
@@ -31,17 +103,43 @@ export async function buildStaticFile(svelteFilePath, outputDir) {
31
103
  }),
32
104
  resolve({
33
105
  browser: true,
34
- dedupe: ['svelte']
106
+ dedupe: ['svelte'],
107
+ preferBuiltins: false,
108
+ moduleDirectories: ['node_modules'],
109
+ modulePaths: [cliNodeModules],
110
+ rootDir: cliNodeModules
35
111
  }),
36
112
  commonjs()
37
- ],
38
- external: ['svelte/internal']
113
+ ]
39
114
  });
40
115
 
41
116
  // Create a temporary directory for SSR
42
117
  const tempDir = path.join(path.dirname(svelteFilePath), '.temp');
43
118
  await fs.mkdir(tempDir, { recursive: true });
44
- const tempSSRFile = path.join(tempDir, 'ssr-bundle.js');
119
+
120
+ // Create package.json for the temp directory
121
+ await fs.writeFile(
122
+ path.join(tempDir, 'package.json'),
123
+ JSON.stringify({
124
+ type: 'module',
125
+ dependencies: {
126
+ svelte: require(path.join(cliNodeModules, 'svelte', 'package.json')).version
127
+ }
128
+ }),
129
+ 'utf-8'
130
+ );
131
+
132
+ // Create symlink to CLI's node_modules
133
+ const tempNodeModules = path.join(tempDir, 'node_modules');
134
+ try {
135
+ await fs.symlink(cliNodeModules, tempNodeModules, 'junction');
136
+ } catch (error) {
137
+ if (error.code !== 'EEXIST') {
138
+ throw error;
139
+ }
140
+ }
141
+
142
+ const tempSSRFile = path.join(tempDir, 'ssr-bundle.mjs');
45
143
 
46
144
  // Generate SSR bundle
47
145
  await ssrBundle.write({
@@ -51,7 +149,7 @@ export async function buildStaticFile(svelteFilePath, outputDir) {
51
149
  });
52
150
 
53
151
  // Import the SSR bundle
54
- const { default: App } = await import(tempSSRFile);
152
+ const { default: App } = await import(`file://${tempSSRFile}`);
55
153
  const { html: initialHtml } = App.render();
56
154
 
57
155
  // Clean up temp files
@@ -66,7 +164,15 @@ export async function buildStaticFile(svelteFilePath, outputDir) {
66
164
  hydratable: true,
67
165
  css: false
68
166
  },
69
- emitCss: true
167
+ emitCss: true,
168
+ preprocess: useTailwind ? {
169
+ style: async ({ content }) => {
170
+ if (!content) return { code: '' };
171
+ const result = await postcss(postcssPlugins)
172
+ .process(content, { from: undefined });
173
+ return { code: result.css };
174
+ }
175
+ } : undefined
70
176
  }),
71
177
  css({
72
178
  output: function(styles) {
@@ -75,7 +181,10 @@ export async function buildStaticFile(svelteFilePath, outputDir) {
75
181
  }),
76
182
  resolve({
77
183
  browser: true,
78
- dedupe: ['svelte']
184
+ dedupe: ['svelte'],
185
+ moduleDirectories: ['node_modules'],
186
+ modulePaths: [cliNodeModules],
187
+ rootDir: cliNodeModules
79
188
  }),
80
189
  commonjs(),
81
190
  terser()
@@ -91,32 +200,10 @@ export async function buildStaticFile(svelteFilePath, outputDir) {
91
200
  });
92
201
 
93
202
  // 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 src="https://unpkg.com/svelte@3.58.0/internal/index.js"></script>
107
- <script>
108
- ${clientCode}
109
- const app = new App({
110
- target: document.getElementById('app'),
111
- hydrate: true
112
- });
113
- </script>
114
- </body>
115
- </html>`;
203
+ 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>`;
116
204
 
117
205
  // Write the output file
118
206
  const outputPath = path.join(outputDir, 'output.html');
119
- await fs.mkdir(outputDir, { recursive: true });
120
207
  await fs.writeFile(outputPath, finalHtml, 'utf-8');
121
208
  } catch (error) {
122
209
  console.error('Build error:', error);
package/cli.js CHANGED
@@ -19,13 +19,11 @@ const packageJson = JSON.parse(
19
19
  )
20
20
  );
21
21
 
22
- // Create readline interface for prompts
23
22
  const rl = createInterface({
24
23
  input: process.stdin,
25
24
  output: process.stdout
26
25
  });
27
26
 
28
- // Promisify readline question
29
27
  const question = (query) => new Promise((resolve) => rl.question(query, resolve));
30
28
 
31
29
  program
@@ -34,28 +32,25 @@ program
34
32
  .version(packageJson.version)
35
33
  .requiredOption('-i, --input <path>', 'Input Svelte file')
36
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')
37
37
  .option('-f, --force', 'Force overwrite without asking');
38
38
 
39
39
  program.parse();
40
40
 
41
41
  const options = program.opts();
42
42
 
43
- async function checkFileExists(filepath) {
43
+ async function loadTailwindConfig(configPath) {
44
+ const fullPath = path.resolve(configPath);
44
45
  try {
45
- await fs.access(filepath);
46
- return true;
47
- } catch {
48
- 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);
49
51
  }
50
52
  }
51
53
 
52
- async function shouldOverwrite(filepath) {
53
- const answer = await question(
54
- chalk.yellow(`File ${filepath} already exists. Overwrite? (y/N): `)
55
- );
56
- return answer.toLowerCase() === 'y';
57
- }
58
-
59
54
  async function validateAndProcess() {
60
55
  try {
61
56
  // Validate input file
@@ -75,33 +70,62 @@ async function validateAndProcess() {
75
70
  process.exit(1);
76
71
  }
77
72
 
78
- // 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
79
96
  let outputDir = process.cwd();
80
97
  if (options.output) {
81
98
  outputDir = path.resolve(options.output);
82
- // Create output directory if it doesn't exist
83
- await fs.mkdir(outputDir, { recursive: true });
84
99
  }
85
100
 
86
101
  const outputPath = path.join(outputDir, 'output.html');
87
102
 
88
- // Check if output file exists and handle overwriting
89
- if (await checkFileExists(outputPath)) {
103
+ // Check if output file exists
104
+ if (await fs.access(outputPath).then(() => true).catch(() => false)) {
90
105
  if (!options.force) {
91
- const shouldProceed = await shouldOverwrite(outputPath);
92
- if (!shouldProceed) {
106
+ const shouldProceed = await question(
107
+ chalk.yellow(`File ${outputPath} already exists. Overwrite? (y/N): `)
108
+ );
109
+ if (shouldProceed.toLowerCase() !== 'y') {
93
110
  console.log(chalk.yellow('Operation cancelled.'));
94
111
  process.exit(0);
95
112
  }
96
113
  }
97
114
  }
98
115
 
99
- console.log(chalk.blue(`Starting ${packageJson.name} v${packageJson.version} build process...`));
116
+ console.log(chalk.blue('Starting build process...'));
100
117
  console.log(chalk.gray(`Input: ${inputPath}`));
101
- 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
+ };
102
127
 
103
- // Process the file
104
- await buildStaticFile(inputPath, outputDir);
128
+ await buildStaticFile(inputPath, outputDir, buildOptions);
105
129
 
106
130
  console.log(chalk.green('\n✨ Build completed successfully!'));
107
131
  console.log(chalk.gray(`Output file: ${outputPath}`));
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "svelte-bundle",
3
- "version": "0.0.12",
4
- "description": "Bundle Svelte files into static HTML files",
3
+ "version": "0.0.22",
4
+ "description": "CLI tool to easily bundle a .svelte file into a single .html file",
5
5
  "type": "module",
6
+ "npm": "svelte-bundle",
6
7
  "bin": {
7
8
  "svelte-bundle": "./cli.js"
8
9
  },
@@ -10,6 +11,10 @@
10
11
  "cli.js",
11
12
  "bundle.js"
12
13
  ],
14
+ "categories": [
15
+ "build-plugins",
16
+ "integrations"
17
+ ],
13
18
  "keywords": [
14
19
  "svelte",
15
20
  "bundle",
@@ -37,12 +42,16 @@
37
42
  "@rollup/plugin-commonjs": "^24.0.1",
38
43
  "@rollup/plugin-node-resolve": "^15.0.2",
39
44
  "@rollup/plugin-terser": "^0.4.0",
45
+ "autoprefixer": "^10.4.20",
40
46
  "chalk": "^5.3.0",
41
47
  "commander": "^11.0.0",
48
+ "cssnano": "^7.0.6",
42
49
  "rollup": "^3.20.0",
43
50
  "rollup-plugin-css-only": "^4.3.0",
51
+ "rollup-plugin-postcss": "^4.0.2",
44
52
  "rollup-plugin-svelte": "^7.1.4",
45
- "svelte": "^3.58.0"
53
+ "svelte": "^3.58.0",
54
+ "tailwindcss": "^3.4.14"
46
55
  },
47
56
  "devDependencies": {
48
57
  "@vitest/coverage-v8": "^2.1.3",