@sprlab/wccompiler 0.12.0 → 0.12.1

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 (3) hide show
  1. package/README.md +46 -7
  2. package/bin/wcc.js +53 -0
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -458,12 +458,13 @@ Consumer (receives data via template props):
458
458
 
459
459
  ## Nested Components
460
460
 
461
- Components can use other components in their templates. Import the child `.wcc` file and use its tag in the template:
461
+ Components can import and use other components in their templates using PascalCase tags:
462
462
 
463
463
  ```html
464
+ <!-- src/nested/wcc-profile.wcc -->
464
465
  <script>
465
466
  import { defineComponent, signal } from 'wcc'
466
- import './wcc-badge.wcc'
467
+ import WccBadge from './wcc-badge.wcc'
467
468
 
468
469
  export default defineComponent({ tag: 'wcc-profile' })
469
470
 
@@ -476,15 +477,17 @@ function increment() {
476
477
 
477
478
  <template>
478
479
  <div class="profile">
479
- <wcc-badge :count="count()" @click="increment"></wcc-badge>
480
+ <WccBadge :count="count()" @click="increment"></WccBadge>
480
481
  </div>
481
482
  </template>
482
483
  ```
483
484
 
484
- - **Manual import**: `import './wcc-child.wcc'` — the compiler registers the child component
485
- - **Auto-detect**: If a custom element tag in the template matches a `.wcc` file in the same directory, it's auto-imported
485
+ - **Named import**: `import WccBadge from './wcc-badge.wcc'` — the PascalCase identifier becomes the tag alias in the template
486
+ - **Side-effect import**: `import './wcc-child.wcc'` registers the child without using it in the template (for programmatic creation)
486
487
  - **Reactive props**: Use `:prop="expr"` to pass reactive data down — updates automatically when the expression changes
487
488
  - **Event listening**: Use `@event="handler"` to listen to custom events emitted by the child
489
+ - **Compile-time validation**: Using a PascalCase tag without a matching import throws an error at build time
490
+ - **Hyphenated tags**: Tags like `<my-element>` without a corresponding import are treated as plain custom elements (no import generated)
488
491
 
489
492
  ## Lifecycle Hooks
490
493
 
@@ -613,12 +616,48 @@ timer.value!.start() // ✅ typed
613
616
  ## CLI
614
617
 
615
618
  ```bash
616
- wcc build # Compile all .wcc files from input/ to output/
617
- wcc dev # Build + watch + live-reload dev server
619
+ wcc build # Compile all .wcc files from input/ to output/
620
+ wcc build --bundle # Compile + produce a single bundle.js (works from file://)
621
+ wcc build --minify # Compile with minification
622
+ wcc build --bundle --minify # Production bundle (smallest output)
623
+ wcc dev # Build + watch + live-reload dev server
618
624
  ```
619
625
 
620
626
  The CLI discovers all `.wcc` files in your source directory and compiles each into a standalone `.js` file.
621
627
 
628
+ ### Bundle Mode
629
+
630
+ The `--bundle` flag produces a single `bundle.js` file that includes all components and their dependencies in one IIFE (Immediately Invoked Function Expression). This file:
631
+
632
+ - Works with `<script src="bundle.js">` (no `type="module"` needed)
633
+ - Works from `file://` protocol (no server required)
634
+ - Includes all child component imports resolved and inlined
635
+ - Includes the reactive runtime
636
+ - Supports `--minify` for production
637
+
638
+ ```html
639
+ <!-- Works by double-clicking the HTML file — no server needed -->
640
+ <!DOCTYPE html>
641
+ <html>
642
+ <body>
643
+ <wcc-my-app></wcc-my-app>
644
+ <script src="dist/bundle.js"></script>
645
+ </body>
646
+ </html>
647
+ ```
648
+
649
+ **When to use `--bundle`:**
650
+ - Static HTML files opened from disk
651
+ - Electron apps loading local files
652
+ - Offline-first applications
653
+ - Quick prototyping without a dev server
654
+ - Distributing a complete app as HTML + JS
655
+
656
+ **When NOT to use `--bundle`:**
657
+ - Apps served via HTTP (use ES modules for better caching)
658
+ - When you need per-component lazy loading
659
+ - When using a bundler like Vite/Webpack (they handle bundling themselves)
660
+
622
661
  ### Configuration
623
662
 
624
663
  Create `wcc.config.js` in your project root:
package/bin/wcc.js CHANGED
@@ -291,6 +291,26 @@ function discoverFiles(dir) {
291
291
  return results;
292
292
  }
293
293
 
294
+ /**
295
+ * Discovers compiled .js entry points in the output directory.
296
+ * Excludes runtime files, stubs, and metadata.
297
+ */
298
+ function discoverCompiledEntries(outputDir) {
299
+ const skip = new Set(['__wcc-signals.js', 'wcc-runtime.js', 'wcc-react.js', 'wcc-vue.js', 'bundle.js']);
300
+ const results = [];
301
+ function walk(dir) {
302
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
303
+ if (entry.isDirectory()) {
304
+ walk(join(dir, entry.name));
305
+ } else if (entry.isFile() && entry.name.endsWith('.js') && !skip.has(entry.name) && !entry.name.endsWith('.d.ts')) {
306
+ results.push(join(dir, entry.name));
307
+ }
308
+ }
309
+ }
310
+ walk(outputDir);
311
+ return results;
312
+ }
313
+
294
314
  async function main() {
295
315
  const cwd = process.cwd();
296
316
  const config = await loadConfig(cwd);
@@ -298,10 +318,43 @@ async function main() {
298
318
  // CLI flags override config
299
319
  if (process.argv.includes('--minify')) config.minify = true;
300
320
  if (process.argv.includes('--comments')) config.comments = true;
321
+ const shouldBundle = process.argv.includes('--bundle');
301
322
 
302
323
  if (command === 'build') {
303
324
  const errors = await build(config, cwd);
304
325
  if (errors > 0) process.exit(1);
326
+
327
+ // Bundle step: produce a single IIFE file from all compiled entry points
328
+ if (shouldBundle) {
329
+ const { build: esbuild } = await import('esbuild');
330
+ const outputDir = resolve(cwd, config.output);
331
+ const entryPoints = discoverCompiledEntries(outputDir);
332
+
333
+ if (entryPoints.length > 0) {
334
+ // Generate a virtual entry that imports all components
335
+ const bundleEntry = join(outputDir, '__bundle-entry.js');
336
+ const imports = entryPoints.map(f => {
337
+ let rel = relative(outputDir, f).replace(/\\/g, '/');
338
+ if (!rel.startsWith('.')) rel = './' + rel;
339
+ return `import '${rel}';`;
340
+ }).join('\n');
341
+ writeFileSync(bundleEntry, imports);
342
+
343
+ await esbuild({
344
+ entryPoints: [bundleEntry],
345
+ bundle: true,
346
+ format: 'iife',
347
+ outfile: join(outputDir, 'bundle.js'),
348
+ minify: !!config.minify,
349
+ });
350
+
351
+ // Clean up temp entry
352
+ const { unlinkSync } = await import('node:fs');
353
+ unlinkSync(bundleEntry);
354
+
355
+ console.log(`Bundled ${entryPoints.length} components → ${config.output}/bundle.js`);
356
+ }
357
+ }
305
358
  } else if (command === 'dev') {
306
359
  await build(config, cwd);
307
360
  const outputDir = resolve(cwd, config.output);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sprlab/wccompiler",
3
- "version": "0.12.0",
3
+ "version": "0.12.1",
4
4
  "description": "Zero-runtime compiler that transforms .wcc single-file components into native web components with signals-based reactivity",
5
5
  "type": "module",
6
6
  "exports": {