puglite 1.0.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 ADDED
@@ -0,0 +1,115 @@
1
+ # Puglite
2
+
3
+ A lightweight, streamlined version of the Pug template engine.
4
+
5
+ ## What is Puglite?
6
+
7
+ Puglite is a refactored version of [Pug](https://github.com/pugjs/pug) with simplified features:
8
+ - ✅ Clean whitespace-sensitive syntax
9
+ - ✅ Tags, classes, IDs, attributes
10
+ - ✅ Compile-time transformation
11
+ - ❌ No logic flow (if/else, loops)
12
+ - ❌ No mixins
13
+ - ❌ No interpolation
14
+
15
+ ## Credits & Thanks
16
+
17
+ **Puglite is built upon [Pug](https://pugjs.org).** All core parsing and compilation code comes from the excellent Pug project. We simply removed features to create a more focused template engine.
18
+
19
+ Huge thanks to:
20
+ - **TJ Holowaychuk** - Pug creator
21
+ - **Forbes Lindesay** - Pug maintainer
22
+ - **The Pug.js team and all contributors**
23
+
24
+ Without their amazing work, Puglite wouldn't exist. ❤️
25
+
26
+ Special thanks to **Adam Miller** for sponsoring the Claude Code usage that helped build Puglite.
27
+
28
+ ## Standalone Usage
29
+
30
+ ```js
31
+ const puglite = require('puglite');
32
+
33
+ const html = puglite.render('.container\n h1 Hello\n p World');
34
+ // <div class="container"><h1>Hello</h1><p>World</p></div>
35
+ ```
36
+
37
+ ## Quick Start with Angular 18+
38
+
39
+ ### 1. Install
40
+ ```bash
41
+ npm install -D puglite @angular-builders/custom-webpack
42
+ ```
43
+
44
+ ### 2. Update `angular.json`
45
+
46
+ Use `puglite:browser` and `puglite:dev-server` builders:
47
+
48
+ ```json
49
+ {
50
+ "architect": {
51
+ "build": {
52
+ "builder": "puglite:browser",
53
+ "options": {
54
+ "outputPath": "dist/my-app",
55
+ "index": "src/index.html",
56
+ "main": "src/main.ts",
57
+ "tsConfig": "tsconfig.app.json"
58
+ }
59
+ },
60
+ "serve": {
61
+ "builder": "puglite:dev-server",
62
+ "configurations": {
63
+ "development": {
64
+ "buildTarget": "my-app:build:development"
65
+ }
66
+ }
67
+ }
68
+ }
69
+ }
70
+ ```
71
+
72
+ **Important:** Puglite requires the **old Angular schema format** with `outputPath`, `index`, and `main`. The new Angular 17+ schema using `browser` instead of `main` is not supported because puglite uses webpack-based builds.
73
+
74
+ ### 3. Use `.pug` Templates
75
+ ```typescript
76
+ @Component({
77
+ selector: 'app-root',
78
+ templateUrl: './app.component.pug'
79
+ })
80
+ export class AppComponent {
81
+ title = 'My App';
82
+ }
83
+ ```
84
+
85
+ **app.component.pug:**
86
+ ```pug
87
+ .container
88
+ h1 {{ title }}
89
+ p Welcome to Puglite!
90
+ ```
91
+
92
+ ## Supported Syntax
93
+
94
+ See EXAMPLES.md
95
+
96
+ ## What's Different from Pug?
97
+
98
+ Puglite **removes** these Pug features:
99
+ - ❌ Logic: `if`, `else`, `unless`, `case`, `when`
100
+ - ❌ Loops: `each`, `while`
101
+ - ❌ Mixins: `mixin`, `+mixin()`
102
+ - ❌ Interpolation: `#{}`, `!{}`
103
+
104
+ ## Why Custom Webpack?
105
+
106
+ Angular 17+ uses esbuild for speed, but esbuild plugins run **after** template validation. Custom-webpack (1.5M downloads/month) uses webpack loaders that transform templates **before** Angular processes them - the only reliable way for compile-time template transformation.
107
+
108
+ ## License
109
+
110
+ MIT
111
+
112
+ ## Links
113
+
114
+ - **Pug**: [pugjs.org](https://pugjs.org) | [GitHub](https://github.com/pugjs/pug)
115
+ - **Custom Webpack**: [GitHub](https://github.com/just-jeb/angular-builders)
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Puglite Angular Webpack Integration
3
+ *
4
+ * Works with @angular-builders/custom-webpack.
5
+ * Export a function that merges pug loader into existing webpack config.
6
+ */
7
+
8
+ const path = require('path');
9
+
10
+ const loaderPath = path.resolve(__dirname, 'loader.js');
11
+
12
+ const pugRule = {
13
+ test: /\.pug$/,
14
+ use: [{ loader: loaderPath }]
15
+ };
16
+
17
+ /**
18
+ * Merge function for @angular-builders/custom-webpack
19
+ * @param {object} config - Existing webpack configuration
20
+ * @returns {object} - Modified webpack configuration
21
+ */
22
+ module.exports = (config) => {
23
+ config.module = config.module || {};
24
+ config.module.rules = config.module.rules || [];
25
+ config.module.rules.push(pugRule);
26
+ return config;
27
+ };
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Webpack loader for Puglite templates
3
+ * Compiles .pug files to HTML at build time
4
+ */
5
+
6
+ module.exports = function pugliteLoader(source) {
7
+ const filename = this.resourcePath;
8
+
9
+ try {
10
+ // Import puglite internal modules
11
+ const lex = require('../lib/lexer');
12
+ const parse = require('../lib/parser');
13
+ const generateCode = require('../lib/code-gen');
14
+ const stripComments = require('../lib/strip-comments');
15
+ const runtimeWrap = require('../lib/runtime-wrap');
16
+
17
+ // Compile: lex → parse → generate → wrap → execute
18
+ const tokens = lex(source, { filename });
19
+ const strippedTokens = stripComments(tokens, { filename });
20
+ const ast = parse(strippedTokens, { filename, src: source });
21
+ const js = generateCode(ast, {
22
+ compileDebug: false,
23
+ pretty: false,
24
+ inlineRuntimeFunctions: false,
25
+ templateName: 'template'
26
+ });
27
+ const templateFn = runtimeWrap(js);
28
+ const html = templateFn();
29
+
30
+ // Return as CommonJS module
31
+ return `module.exports = ${JSON.stringify(html)};`;
32
+
33
+ } catch (error) {
34
+ this.emitError(new Error(`Puglite compilation failed for ${filename}: ${error.message}`));
35
+ return `module.exports = "";`;
36
+ }
37
+ };
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Puglite Browser Builder
3
+ *
4
+ * Wraps @angular-builders/custom-webpack browser builder and auto-injects
5
+ * the puglite webpack configuration for .pug template support.
6
+ */
7
+
8
+ const path = require('path');
9
+ const { createRequire } = require('module');
10
+ const debug = require('util').debuglog('puglite');
11
+
12
+ const projectRequire = createRequire(path.join(process.cwd(), 'package.json'));
13
+
14
+ const loaderPath = path.resolve(__dirname, '../../angular-webpack/loader.js');
15
+
16
+ const pugRule = {
17
+ test: /\.pug$/,
18
+ use: [{ loader: loaderPath }]
19
+ };
20
+
21
+ function buildBrowser(options, context) {
22
+ debug('buildBrowser() called');
23
+ const { getTransforms } = projectRequire('@angular-builders/custom-webpack');
24
+ const { executeBrowserBuilder } = projectRequire('@angular-devkit/build-angular');
25
+
26
+ const pugWebpackTransform = (config) => {
27
+ config.module = config.module || {};
28
+ config.module.rules = config.module.rules || [];
29
+ config.module.rules.push(pugRule);
30
+ return config;
31
+ };
32
+
33
+ const transforms = getTransforms(options, context);
34
+ const originalWebpackTransform = transforms.webpackConfiguration;
35
+
36
+ transforms.webpackConfiguration = async (config) => {
37
+ let result = config;
38
+ if (originalWebpackTransform) {
39
+ result = await originalWebpackTransform(result);
40
+ }
41
+ return pugWebpackTransform(result);
42
+ };
43
+
44
+ return executeBrowserBuilder(options, context, transforms);
45
+ }
46
+
47
+ const { createBuilder } = projectRequire('@angular-devkit/architect');
48
+ module.exports = createBuilder(buildBrowser);
49
+ module.exports.default = module.exports;
@@ -0,0 +1,138 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema",
3
+ "title": "Puglite Browser Builder",
4
+ "description": "Build browser application with Puglite template support",
5
+ "type": "object",
6
+ "properties": {
7
+ "outputPath": {
8
+ "type": "string",
9
+ "description": "The full path for the new output directory."
10
+ },
11
+ "index": {
12
+ "description": "Configures the generation of the application's HTML index.",
13
+ "oneOf": [
14
+ { "type": "string" },
15
+ {
16
+ "type": "object",
17
+ "properties": {
18
+ "input": { "type": "string" },
19
+ "output": { "type": "string", "default": "index.html" }
20
+ },
21
+ "required": ["input"]
22
+ }
23
+ ]
24
+ },
25
+ "main": {
26
+ "type": "string",
27
+ "description": "The full path for the main entry point to the app."
28
+ },
29
+ "tsConfig": {
30
+ "type": "string",
31
+ "description": "The full path for the TypeScript configuration file."
32
+ },
33
+ "assets": {
34
+ "type": "array",
35
+ "description": "List of static application assets.",
36
+ "default": [],
37
+ "items": {
38
+ "oneOf": [
39
+ { "type": "string" },
40
+ {
41
+ "type": "object",
42
+ "properties": {
43
+ "glob": { "type": "string" },
44
+ "input": { "type": "string" },
45
+ "output": { "type": "string" },
46
+ "ignore": { "type": "array", "items": { "type": "string" } }
47
+ },
48
+ "required": ["glob", "input"]
49
+ }
50
+ ]
51
+ }
52
+ },
53
+ "styles": {
54
+ "type": "array",
55
+ "description": "Global styles to be included in the build.",
56
+ "default": [],
57
+ "items": {
58
+ "oneOf": [
59
+ { "type": "string" },
60
+ {
61
+ "type": "object",
62
+ "properties": {
63
+ "input": { "type": "string" },
64
+ "bundleName": { "type": "string" },
65
+ "inject": { "type": "boolean", "default": true }
66
+ },
67
+ "required": ["input"]
68
+ }
69
+ ]
70
+ }
71
+ },
72
+ "scripts": {
73
+ "type": "array",
74
+ "description": "Global scripts to be included in the build.",
75
+ "default": []
76
+ },
77
+ "polyfills": {
78
+ "description": "Polyfills to be included in the build.",
79
+ "oneOf": [
80
+ { "type": "string" },
81
+ { "type": "array", "items": { "type": "string" } }
82
+ ]
83
+ },
84
+ "inlineStyleLanguage": {
85
+ "type": "string",
86
+ "description": "The stylesheet language to use for inline styles.",
87
+ "enum": ["css", "less", "sass", "scss"],
88
+ "default": "css"
89
+ },
90
+ "optimization": {
91
+ "description": "Enables optimization of the build output.",
92
+ "oneOf": [{ "type": "boolean" }, { "type": "object" }],
93
+ "default": true
94
+ },
95
+ "sourceMap": {
96
+ "description": "Output source maps for scripts and styles.",
97
+ "oneOf": [{ "type": "boolean" }, { "type": "object" }],
98
+ "default": false
99
+ },
100
+ "outputHashing": {
101
+ "type": "string",
102
+ "description": "Define the output filename cache-busting hashing mode.",
103
+ "enum": ["none", "all", "media", "bundles"],
104
+ "default": "none"
105
+ },
106
+ "extractLicenses": {
107
+ "type": "boolean",
108
+ "description": "Extract all licenses in a separate file.",
109
+ "default": true
110
+ },
111
+ "budgets": {
112
+ "type": "array",
113
+ "description": "Budget thresholds to ensure parts of your application stay within boundaries.",
114
+ "default": []
115
+ },
116
+ "aot": {
117
+ "type": "boolean",
118
+ "description": "Build using Ahead of Time compilation.",
119
+ "default": true
120
+ },
121
+ "progress": {
122
+ "type": "boolean",
123
+ "description": "Log progress to the console while building.",
124
+ "default": true
125
+ },
126
+ "watch": {
127
+ "type": "boolean",
128
+ "description": "Run build when files change.",
129
+ "default": false
130
+ },
131
+ "poll": {
132
+ "type": "number",
133
+ "description": "Enable and define the file watching poll time period in milliseconds."
134
+ }
135
+ },
136
+ "additionalProperties": true,
137
+ "required": ["outputPath", "index", "main", "tsConfig"]
138
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Puglite Dev Server Builder
3
+ *
4
+ * Wraps Angular's dev server with pug template support.
5
+ */
6
+
7
+ const path = require('path');
8
+ const { createRequire } = require('module');
9
+ const debug = require('util').debuglog('puglite');
10
+
11
+ const projectRequire = createRequire(path.join(process.cwd(), 'package.json'));
12
+
13
+ const loaderPath = path.resolve(__dirname, '../../angular-webpack/loader.js');
14
+
15
+ const pugRule = {
16
+ test: /\.pug$/,
17
+ use: [{ loader: loaderPath }]
18
+ };
19
+
20
+ function serveBrowser(options, context) {
21
+ debug('serveBrowser() called');
22
+ const { getTransforms } = projectRequire('@angular-builders/custom-webpack');
23
+ const { executeDevServerBuilder } = projectRequire('@angular-devkit/build-angular');
24
+
25
+ const pugWebpackTransform = (config) => {
26
+ config.module = config.module || {};
27
+ config.module.rules = config.module.rules || [];
28
+ config.module.rules.push(pugRule);
29
+ return config;
30
+ };
31
+
32
+ const transforms = getTransforms(options, context);
33
+ const originalWebpackTransform = transforms.webpackConfiguration;
34
+
35
+ transforms.webpackConfiguration = async (config) => {
36
+ let result = config;
37
+ if (originalWebpackTransform) {
38
+ result = await originalWebpackTransform(result);
39
+ }
40
+ return pugWebpackTransform(result);
41
+ };
42
+
43
+ return executeDevServerBuilder(options, context, transforms);
44
+ }
45
+
46
+ const { createBuilder } = projectRequire('@angular-devkit/architect');
47
+ module.exports = createBuilder(serveBrowser);
48
+ module.exports.default = module.exports;
@@ -0,0 +1,98 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema",
3
+ "title": "Puglite Dev Server Builder",
4
+ "description": "Development server with Puglite template support",
5
+ "type": "object",
6
+ "properties": {
7
+ "buildTarget": {
8
+ "type": "string",
9
+ "description": "A build builder target to serve in the format of `project:target[:configuration]`.",
10
+ "pattern": "^[^:\\s]*:[^:\\s]*(:[^\\s]+)?$"
11
+ },
12
+ "port": {
13
+ "type": "number",
14
+ "description": "Port to listen on.",
15
+ "default": 4200
16
+ },
17
+ "host": {
18
+ "type": "string",
19
+ "description": "Host to listen on.",
20
+ "default": "localhost"
21
+ },
22
+ "proxyConfig": {
23
+ "type": "string",
24
+ "description": "Proxy configuration file."
25
+ },
26
+ "ssl": {
27
+ "type": "boolean",
28
+ "description": "Serve using HTTPS.",
29
+ "default": false
30
+ },
31
+ "sslKey": {
32
+ "type": "string",
33
+ "description": "SSL key to use for serving HTTPS."
34
+ },
35
+ "sslCert": {
36
+ "type": "string",
37
+ "description": "SSL certificate to use for serving HTTPS."
38
+ },
39
+ "headers": {
40
+ "type": "object",
41
+ "description": "Custom HTTP headers to be added to all responses.",
42
+ "additionalProperties": {
43
+ "type": "string"
44
+ }
45
+ },
46
+ "open": {
47
+ "type": "boolean",
48
+ "description": "Opens the url in default browser.",
49
+ "default": false,
50
+ "alias": "o"
51
+ },
52
+ "verbose": {
53
+ "type": "boolean",
54
+ "description": "Adds more details to output logging."
55
+ },
56
+ "liveReload": {
57
+ "type": "boolean",
58
+ "description": "Whether to reload the page on change, using live-reload.",
59
+ "default": true
60
+ },
61
+ "publicHost": {
62
+ "type": "string",
63
+ "description": "The URL that the browser client should use to connect to the development server."
64
+ },
65
+ "allowedHosts": {
66
+ "type": "array",
67
+ "description": "List of hosts that are allowed to access the dev server.",
68
+ "default": [],
69
+ "items": {
70
+ "type": "string"
71
+ }
72
+ },
73
+ "servePath": {
74
+ "type": "string",
75
+ "description": "The pathname where the application will be served."
76
+ },
77
+ "disableHostCheck": {
78
+ "type": "boolean",
79
+ "description": "Don't verify connected clients are part of allowed hosts.",
80
+ "default": false
81
+ },
82
+ "hmr": {
83
+ "type": "boolean",
84
+ "description": "Enable hot module replacement."
85
+ },
86
+ "watch": {
87
+ "type": "boolean",
88
+ "description": "Rebuild on change.",
89
+ "default": true
90
+ },
91
+ "poll": {
92
+ "type": "number",
93
+ "description": "Enable and define the file watching poll time period in milliseconds."
94
+ }
95
+ },
96
+ "additionalProperties": false,
97
+ "required": ["buildTarget"]
98
+ }
@@ -0,0 +1,82 @@
1
+ # Puglite Karma Test Builder
2
+
3
+ Karma test runner with Puglite (`.pug`) template support for Angular projects.
4
+
5
+ ## Usage
6
+
7
+ Update your `angular.json` to use the puglite karma builder:
8
+
9
+ ```json
10
+ {
11
+ "projects": {
12
+ "your-project": {
13
+ "architect": {
14
+ "test": {
15
+ "builder": "puglite:karma",
16
+ "options": {
17
+ "karmaConfig": "karma.conf.js",
18
+ "main": "src/test.ts",
19
+ "polyfills": ["zone.js", "zone.js/testing"],
20
+ "tsConfig": "tsconfig.spec.json",
21
+ "assets": ["src/favicon.ico", "src/assets"],
22
+ "styles": ["src/styles.scss"],
23
+ "scripts": []
24
+ }
25
+ }
26
+ }
27
+ }
28
+ }
29
+ }
30
+ ```
31
+
32
+ ## Features
33
+
34
+ - ✅ Automatic `.pug` template loading via webpack
35
+ - ✅ Works with existing Karma configuration
36
+ - ✅ Full Angular CLI test options support
37
+ - ✅ Code coverage support
38
+ - ✅ Watch mode support
39
+
40
+ ## How It Works
41
+
42
+ The builder wraps Angular's standard Karma builder and injects webpack configuration to handle `.pug` templates using the puglite loader. This allows you to:
43
+
44
+ 1. Use `.pug` files as Angular component templates
45
+ 2. Run unit tests with Karma/Jasmine
46
+ 3. Test components that use `.pug` templates
47
+
48
+ ## Example Component
49
+
50
+ ```typescript
51
+ @Component({
52
+ selector: 'app-example',
53
+ templateUrl: './example.component.pug',
54
+ styleUrls: ['./example.component.scss']
55
+ })
56
+ export class ExampleComponent {}
57
+ ```
58
+
59
+ ```pug
60
+ // example.component.pug
61
+ .container
62
+ h1 Hello World
63
+ p This is a pug template
64
+ ```
65
+
66
+ ## Running Tests
67
+
68
+ ```bash
69
+ ng test
70
+ ```
71
+
72
+ Or with code coverage:
73
+
74
+ ```bash
75
+ ng test --code-coverage
76
+ ```
77
+
78
+ ## Requirements
79
+
80
+ - Angular 18+
81
+ - Karma
82
+ - `@angular-builders/custom-webpack` (peer dependency)
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Puglite Karma Test Builder
3
+ *
4
+ * Wraps Angular's Karma test runner with pug template support.
5
+ * Follows @angular-builders/custom-webpack karma pattern.
6
+ */
7
+
8
+ const path = require('path');
9
+ const { createRequire } = require('module');
10
+ const debug = require('util').debuglog('puglite');
11
+
12
+ const projectRequire = createRequire(path.join(process.cwd(), 'package.json'));
13
+
14
+ const loaderPath = path.resolve(__dirname, '../../angular-webpack/loader.js');
15
+
16
+ const pugRule = {
17
+ test: /\.pug$/,
18
+ use: [{ loader: loaderPath }]
19
+ };
20
+
21
+ const buildKarma = (options, context) => {
22
+ debug('buildKarma() called');
23
+ const { executeKarmaBuilder } = projectRequire('@angular-devkit/build-angular');
24
+
25
+ const pugWebpackTransform = (config) => {
26
+ debug('applying pug webpack transform, rules: %d', (config.module?.rules?.length || 0));
27
+ config.module = config.module || {};
28
+ config.module.rules = config.module.rules || [];
29
+ config.module.rules.push(pugRule);
30
+ return config;
31
+ };
32
+
33
+ return executeKarmaBuilder(options, context, {
34
+ webpackConfiguration: pugWebpackTransform
35
+ });
36
+ };
37
+
38
+ const { createBuilder } = projectRequire('@angular-devkit/architect');
39
+ module.exports = createBuilder(buildKarma);
40
+ module.exports.default = module.exports;