pulse-js-framework 1.7.31 → 1.7.33

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.
@@ -0,0 +1,228 @@
1
+ /**
2
+ * Pulse Webpack Loader
3
+ *
4
+ * Enables .pulse file support in Webpack projects
5
+ * Extracts CSS and compiles to JavaScript with source maps
6
+ *
7
+ * CSS Preprocessor Support:
8
+ * - If `sass`, `less`, or `stylus` is installed in the user's project,
9
+ * preprocessor syntax in style blocks is automatically compiled
10
+ * - No configuration needed - just install the preprocessor package
11
+ *
12
+ * Usage in webpack.config.js:
13
+ * ```js
14
+ * module.exports = {
15
+ * module: {
16
+ * rules: [
17
+ * {
18
+ * test: /\.pulse$/,
19
+ * use: [
20
+ * 'style-loader', // or mini-css-extract-plugin
21
+ * 'css-loader',
22
+ * 'pulse-js-framework/loader/webpack-loader'
23
+ * ]
24
+ * }
25
+ * ]
26
+ * }
27
+ * };
28
+ * ```
29
+ */
30
+
31
+ import { compile } from '../compiler/index.js';
32
+ import {
33
+ preprocessStylesSync,
34
+ isSassAvailable,
35
+ isLessAvailable,
36
+ isStylusAvailable,
37
+ getSassVersion,
38
+ getLessVersion,
39
+ getStylusVersion,
40
+ detectPreprocessor
41
+ } from '../compiler/preprocessor.js';
42
+ import { dirname } from 'path';
43
+
44
+ // Cache for preprocessor availability checks
45
+ let preprocessorCache = null;
46
+
47
+ /**
48
+ * Check available preprocessors once
49
+ */
50
+ function checkPreprocessors() {
51
+ if (preprocessorCache) return preprocessorCache;
52
+
53
+ preprocessorCache = {
54
+ sass: isSassAvailable(),
55
+ less: isLessAvailable(),
56
+ stylus: isStylusAvailable()
57
+ };
58
+
59
+ return preprocessorCache;
60
+ }
61
+
62
+ /**
63
+ * Webpack loader for .pulse files
64
+ * @this {import('webpack').LoaderContext}
65
+ */
66
+ export default function pulseLoader(source) {
67
+ const callback = this.async();
68
+ const options = this.getOptions() || {};
69
+
70
+ const {
71
+ sourceMap = true,
72
+ sass: sassOptions = {},
73
+ less: lessOptions = {},
74
+ stylus: stylusOptions = {}
75
+ } = options;
76
+
77
+ // Mark as cacheable
78
+ this.cacheable?.();
79
+
80
+ try {
81
+ // Compile .pulse to JavaScript
82
+ const result = compile(source, {
83
+ runtime: 'pulse-js-framework/runtime',
84
+ sourceMap,
85
+ filename: this.resourcePath
86
+ });
87
+
88
+ if (!result.success) {
89
+ const errors = result.errors.map(e =>
90
+ `${e.message}${e.line ? ` at line ${e.line}` : ''}`
91
+ ).join('\n');
92
+
93
+ callback(new Error(`Pulse compilation failed:\n${errors}`));
94
+ return;
95
+ }
96
+
97
+ let outputCode = result.code;
98
+ let outputMap = result.map;
99
+
100
+ // Extract CSS from compiled output
101
+ const stylesMatch = outputCode.match(/const styles = `([\s\S]*?)`;/);
102
+
103
+ if (stylesMatch) {
104
+ let css = stylesMatch[1];
105
+
106
+ // Check available preprocessors
107
+ const available = checkPreprocessors();
108
+ const preprocessor = detectPreprocessor(css);
109
+
110
+ // Preprocess if preprocessor detected and available
111
+ if (preprocessor !== 'none' && available[preprocessor]) {
112
+ try {
113
+ const preprocessorOptions = {
114
+ sass: sassOptions,
115
+ less: lessOptions,
116
+ stylus: stylusOptions
117
+ }[preprocessor];
118
+
119
+ const preprocessed = preprocessStylesSync(css, {
120
+ filename: this.resourcePath,
121
+ loadPaths: [dirname(this.resourcePath), ...(preprocessorOptions.loadPaths || [])],
122
+ compressed: preprocessorOptions.compressed || false,
123
+ preprocessor // Force detected preprocessor
124
+ });
125
+
126
+ css = preprocessed.css;
127
+
128
+ // Log preprocessor usage in verbose mode
129
+ if (preprocessorOptions.verbose) {
130
+ console.log(`[Pulse] Compiled ${preprocessor.toUpperCase()} in ${this.resourcePath}`);
131
+ }
132
+ } catch (preprocessorError) {
133
+ // Emit warning but continue with original CSS
134
+ this.emitWarning(
135
+ new Error(`${preprocessor.toUpperCase()} compilation warning: ${preprocessorError.message}`)
136
+ );
137
+ }
138
+ }
139
+
140
+ // Emit CSS as separate file or inline
141
+ if (options.extractCss !== false) {
142
+ // Default: emit CSS for css-loader to process
143
+ // This allows Webpack's CSS pipeline to handle it
144
+ this.emitFile?.(
145
+ this.resourcePath.replace(/\.pulse$/, '.pulse.css'),
146
+ css
147
+ );
148
+
149
+ // Replace inline CSS injection with import statement
150
+ // css-loader will process the CSS and style-loader will inject it
151
+ outputCode = outputCode.replace(
152
+ /\/\/ Styles\nconst styles = `[\s\S]*?`;\n\/\/ Inject styles\nconst styleEl = document\.createElement\("style"\);\nstyleEl\.textContent = styles;\ndocument\.head\.appendChild\(styleEl\);/,
153
+ `// Styles extracted - handled by css-loader\nimport "./${this.resourcePath.split('/').pop().replace(/\.pulse$/, '.pulse.css')}";`
154
+ );
155
+ }
156
+ // else: keep inline CSS injection (useful for development)
157
+ }
158
+
159
+ // Add HMR support if Webpack HMR is enabled
160
+ if (this.hot && options.hmr !== false) {
161
+ outputCode += `\n${generateHMRCode(this.resourcePath)}`;
162
+ }
163
+
164
+ // Only pass source map if enabled in options
165
+ callback(null, outputCode, sourceMap ? outputMap : null);
166
+ } catch (error) {
167
+ callback(new Error(`Pulse loader error: ${error.message}`));
168
+ }
169
+ }
170
+
171
+ /**
172
+ * Generate HMR (Hot Module Replacement) code for Webpack
173
+ */
174
+ function generateHMRCode(resourcePath) {
175
+ return `
176
+ // Webpack HMR
177
+ if (module.hot) {
178
+ module.hot.accept();
179
+
180
+ // Cleanup on module replacement
181
+ module.hot.dispose((data) => {
182
+ // Store state for preservation
183
+ if (typeof __PULSE_HMR_STATE__ !== 'undefined') {
184
+ data.pulseState = __PULSE_HMR_STATE__;
185
+ }
186
+ });
187
+
188
+ // Restore state after replacement
189
+ if (module.hot.data && module.hot.data.pulseState) {
190
+ if (typeof __PULSE_HMR_RESTORE__ !== 'undefined') {
191
+ __PULSE_HMR_RESTORE__(module.hot.data.pulseState);
192
+ }
193
+ }
194
+ }
195
+ `;
196
+ }
197
+
198
+ /**
199
+ * Pitch loader - runs before other loaders
200
+ * Used to log preprocessor availability
201
+ */
202
+ export function pitch() {
203
+ const available = checkPreprocessors();
204
+ const options = this.getOptions() || {};
205
+
206
+ // Log preprocessor availability once on first run
207
+ if (!pulseLoader._logged && options.verbose !== false) {
208
+ const preprocessors = [];
209
+ if (available.sass) {
210
+ preprocessors.push(`SASS ${getSassVersion() || 'unknown'}`);
211
+ }
212
+ if (available.less) {
213
+ preprocessors.push(`LESS ${getLessVersion() || 'unknown'}`);
214
+ }
215
+ if (available.stylus) {
216
+ preprocessors.push(`Stylus ${getStylusVersion() || 'unknown'}`);
217
+ }
218
+
219
+ if (preprocessors.length > 0) {
220
+ console.log(`[Pulse Webpack] Preprocessor support: ${preprocessors.join(', ')}`);
221
+ }
222
+
223
+ pulseLoader._logged = true;
224
+ }
225
+ }
226
+
227
+ // Export for CommonJS compatibility
228
+ export const raw = false; // Return code as string, not Buffer
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pulse-js-framework",
3
- "version": "1.7.31",
3
+ "version": "1.7.33",
4
4
  "description": "A declarative DOM framework with CSS selector-based structure and reactive pulsations",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -88,11 +88,17 @@
88
88
  "./compiler/lexer": "./compiler/lexer.js",
89
89
  "./compiler/parser": "./compiler/parser.js",
90
90
  "./compiler/transformer": "./compiler/transformer.js",
91
+ "./compiler/preprocessor": "./compiler/preprocessor.js",
91
92
  "./core/errors": "./runtime/errors.js",
92
93
  "./vite": {
93
94
  "types": "./types/index.d.ts",
94
95
  "default": "./loader/vite-plugin.js"
95
96
  },
97
+ "./webpack": "./loader/webpack-loader.js",
98
+ "./rollup": "./loader/rollup-plugin.js",
99
+ "./esbuild": "./loader/esbuild-plugin.js",
100
+ "./parcel": "./loader/parcel-plugin.js",
101
+ "./swc": "./loader/swc-plugin.js",
96
102
  "./mobile": "./mobile/bridge/pulse-native.js",
97
103
  "./package.json": "./package.json"
98
104
  },
@@ -109,10 +115,11 @@
109
115
  "LICENSE"
110
116
  ],
111
117
  "scripts": {
112
- "test": "npm run test:compiler && npm run test:sourcemap && npm run test:css-parsing && npm run test:pulse && npm run test:dom && npm run test:dom-element && npm run test:dom-list && npm run test:dom-conditional && npm run test:dom-lifecycle && npm run test:dom-selector && npm run test:dom-adapter && npm run test:dom-advanced && npm run test:enhanced-mock-adapter && npm run test:router && npm run test:store && npm run test:context && npm run test:hmr && npm run test:lint && npm run test:format && npm run test:analyze && npm run test:cli && npm run test:cli-ui && npm run test:cli-create && npm run test:lru-cache && npm run test:utils && npm run test:utils-coverage && npm run test:docs && npm run test:docs-nav && npm run test:async && npm run test:async-coverage && npm run test:form && npm run test:http && npm run test:devtools && npm run test:native && npm run test:a11y && npm run test:a11y-enhanced && npm run test:logger && npm run test:logger-prod && npm run test:errors && npm run test:security && npm run test:websocket && npm run test:graphql && npm run test:graphql-coverage && npm run test:doctor && npm run test:scaffold && npm run test:test-runner && npm run test:build && npm run test:integration && npm run test:context-stress && npm run test:form-edge-cases && npm run test:graphql-subscriptions && npm run test:http-edge-cases && npm run test:integration-advanced && npm run test:websocket-stress && npm run test:ssr && npm run test:ssr-hydrator",
118
+ "test": "node scripts/run-all-tests.js",
113
119
  "test:compiler": "node test/compiler.test.js",
114
120
  "test:sourcemap": "node test/sourcemap.test.js",
115
121
  "test:css-parsing": "node test/css-parsing.test.js",
122
+ "test:preprocessor": "node test/preprocessor.test.js",
116
123
  "test:pulse": "node test/pulse.test.js",
117
124
  "test:dom": "node test/dom.test.js",
118
125
  "test:dom-element": "node test/dom-element.test.js",
@@ -166,6 +173,11 @@
166
173
  "test:websocket-stress": "node test/websocket-stress.test.js",
167
174
  "test:ssr": "node test/ssr.test.js",
168
175
  "test:ssr-hydrator": "node test/ssr-hydrator.test.js",
176
+ "test:webpack-loader": "node test/webpack-loader.test.js",
177
+ "test:rollup-plugin": "node test/rollup-plugin.test.js",
178
+ "test:esbuild-plugin": "node test/esbuild-plugin.test.js",
179
+ "test:parcel-plugin": "node test/parcel-plugin.test.js",
180
+ "test:swc-plugin": "node test/swc-plugin.test.js",
169
181
  "build:netlify": "node scripts/build-netlify.js",
170
182
  "version": "node scripts/sync-version.js",
171
183
  "docs": "node cli/index.js dev docs"