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,251 @@
1
+ /**
2
+ * Pulse ESBuild Plugin
3
+ *
4
+ * Enables .pulse file support in ESBuild 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 build script:
13
+ * ```js
14
+ * import * as esbuild from 'esbuild';
15
+ * import pulsePlugin from 'pulse-js-framework/esbuild';
16
+ *
17
+ * await esbuild.build({
18
+ * entryPoints: ['src/main.js'],
19
+ * bundle: true,
20
+ * outfile: 'dist/bundle.js',
21
+ * plugins: [
22
+ * pulsePlugin({
23
+ * sourceMap: true,
24
+ * extractCss: 'dist/bundle.css',
25
+ * sass: {
26
+ * loadPaths: ['src/styles']
27
+ * }
28
+ * })
29
+ * ]
30
+ * });
31
+ * ```
32
+ */
33
+
34
+ import { compile } from '../compiler/index.js';
35
+ import {
36
+ preprocessStylesSync,
37
+ isSassAvailable,
38
+ isLessAvailable,
39
+ isStylusAvailable,
40
+ getSassVersion,
41
+ getLessVersion,
42
+ getStylusVersion,
43
+ detectPreprocessor
44
+ } from '../compiler/preprocessor.js';
45
+ import { dirname } from 'path';
46
+ import { readFileSync, writeFileSync, mkdirSync } from 'fs';
47
+ import { resolve } from 'path';
48
+
49
+ // Cache for preprocessor availability checks
50
+ let preprocessorCache = null;
51
+
52
+ /**
53
+ * Check available preprocessors once
54
+ */
55
+ function checkPreprocessors() {
56
+ if (preprocessorCache) return preprocessorCache;
57
+
58
+ preprocessorCache = {
59
+ sass: isSassAvailable(),
60
+ less: isLessAvailable(),
61
+ stylus: isStylusAvailable()
62
+ };
63
+
64
+ return preprocessorCache;
65
+ }
66
+
67
+ /**
68
+ * Create Pulse ESBuild plugin
69
+ */
70
+ export default function pulsePlugin(options = {}) {
71
+ const {
72
+ sourceMap = true,
73
+ extractCss = null, // Path to output CSS file, or null for inline
74
+ sass: sassOptions = {},
75
+ less: lessOptions = {},
76
+ stylus: stylusOptions = {}
77
+ } = options;
78
+
79
+ // Accumulated CSS from all .pulse files
80
+ let accumulatedCss = '';
81
+ let buildStarted = false;
82
+
83
+ return {
84
+ name: 'pulse',
85
+
86
+ setup(build) {
87
+ // Log preprocessor availability on first setup
88
+ if (!buildStarted) {
89
+ const available = checkPreprocessors();
90
+ const preprocessors = [];
91
+
92
+ if (available.sass) {
93
+ preprocessors.push(`SASS ${getSassVersion() || 'unknown'}`);
94
+ }
95
+ if (available.less) {
96
+ preprocessors.push(`LESS ${getLessVersion() || 'unknown'}`);
97
+ }
98
+ if (available.stylus) {
99
+ preprocessors.push(`Stylus ${getStylusVersion() || 'unknown'}`);
100
+ }
101
+
102
+ if (preprocessors.length > 0) {
103
+ console.log(`[Pulse ESBuild] Preprocessor support: ${preprocessors.join(', ')}`);
104
+ }
105
+
106
+ buildStarted = true;
107
+ }
108
+
109
+ // Reset accumulated CSS on each build
110
+ build.onStart(() => {
111
+ accumulatedCss = '';
112
+ });
113
+
114
+ // Transform .pulse files
115
+ build.onLoad({ filter: /\.pulse$/ }, async (args) => {
116
+ try {
117
+ // Read source file
118
+ const source = readFileSync(args.path, 'utf8');
119
+
120
+ // Compile .pulse to JavaScript
121
+ const result = compile(source, {
122
+ runtime: 'pulse-js-framework/runtime',
123
+ sourceMap,
124
+ filename: args.path
125
+ });
126
+
127
+ if (!result.success) {
128
+ const errors = result.errors.map(e =>
129
+ `${e.message}${e.line ? ` at line ${e.line}` : ''}`
130
+ ).join('\n');
131
+
132
+ return {
133
+ errors: [{
134
+ text: `Pulse compilation failed:\n${errors}`,
135
+ location: { file: args.path }
136
+ }]
137
+ };
138
+ }
139
+
140
+ let outputCode = result.code;
141
+
142
+ // Extract CSS from compiled output
143
+ const stylesMatch = outputCode.match(/const styles = `([\s\S]*?)`;/);
144
+
145
+ if (stylesMatch) {
146
+ let css = stylesMatch[1];
147
+
148
+ // Check available preprocessors
149
+ const available = checkPreprocessors();
150
+ const preprocessor = detectPreprocessor(css);
151
+
152
+ // Preprocess if preprocessor detected and available
153
+ if (preprocessor !== 'none' && available[preprocessor]) {
154
+ try {
155
+ const preprocessorOptions = {
156
+ sass: sassOptions,
157
+ less: lessOptions,
158
+ stylus: stylusOptions
159
+ }[preprocessor];
160
+
161
+ const preprocessed = preprocessStylesSync(css, {
162
+ filename: args.path,
163
+ loadPaths: [dirname(args.path), ...(preprocessorOptions.loadPaths || [])],
164
+ compressed: preprocessorOptions.compressed || false,
165
+ preprocessor // Force detected preprocessor
166
+ });
167
+
168
+ css = preprocessed.css;
169
+
170
+ // Log preprocessor usage in verbose mode
171
+ if (preprocessorOptions.verbose) {
172
+ console.log(`[Pulse] Compiled ${preprocessor.toUpperCase()} in ${args.path}`);
173
+ }
174
+ } catch (preprocessorError) {
175
+ // Emit warning but continue with original CSS
176
+ return {
177
+ warnings: [{
178
+ text: `${preprocessor.toUpperCase()} compilation warning: ${preprocessorError.message}`,
179
+ location: { file: args.path }
180
+ }],
181
+ contents: outputCode,
182
+ loader: 'js'
183
+ };
184
+ }
185
+ }
186
+
187
+ if (extractCss) {
188
+ // Accumulate CSS for later emission
189
+ accumulatedCss += `/* ${args.path} */\n${css}\n\n`;
190
+
191
+ // Remove inline CSS injection from output
192
+ outputCode = outputCode.replace(
193
+ /\/\/ Styles\nconst styles = `[\s\S]*?`;\n\/\/ Inject styles\nconst styleEl = document\.createElement\("style"\);\nstyleEl\.textContent = styles;\ndocument\.head\.appendChild\(styleEl\);/,
194
+ '// Styles extracted to CSS file'
195
+ );
196
+ }
197
+ // else: keep inline CSS injection
198
+ }
199
+
200
+ return {
201
+ contents: outputCode,
202
+ loader: 'js',
203
+ watchFiles: [args.path]
204
+ };
205
+ } catch (error) {
206
+ return {
207
+ errors: [{
208
+ text: `Pulse plugin error: ${error.message}`,
209
+ location: { file: args.path }
210
+ }]
211
+ };
212
+ }
213
+ });
214
+
215
+ // Emit accumulated CSS as separate file
216
+ build.onEnd((result) => {
217
+ if (extractCss && accumulatedCss && result.errors.length === 0) {
218
+ try {
219
+ // Resolve output path
220
+ const cssPath = resolve(extractCss);
221
+
222
+ // Create directory if it doesn't exist
223
+ const cssDir = dirname(cssPath);
224
+ mkdirSync(cssDir, { recursive: true });
225
+
226
+ // Write CSS file
227
+ writeFileSync(cssPath, accumulatedCss, 'utf8');
228
+ console.log(`[Pulse] Emitted CSS to ${extractCss}`);
229
+ } catch (writeError) {
230
+ console.error(`[Pulse] Failed to write CSS file: ${writeError.message}`);
231
+ }
232
+ }
233
+ });
234
+
235
+ // Resolve .pulse imports as .js if needed
236
+ build.onResolve({ filter: /\.js$/ }, (args) => {
237
+ // Check if there's a corresponding .pulse file
238
+ const pulsePath = args.path.replace(/\.js$/, '.pulse');
239
+ const resolvedPulsePath = resolve(args.resolveDir, pulsePath);
240
+
241
+ try {
242
+ readFileSync(resolvedPulsePath);
243
+ return { path: resolvedPulsePath };
244
+ } catch {
245
+ // No .pulse file, continue with normal resolution
246
+ return null;
247
+ }
248
+ });
249
+ }
250
+ };
251
+ }
@@ -0,0 +1,216 @@
1
+ /**
2
+ * Pulse Parcel Plugin (Transformer)
3
+ *
4
+ * Enables .pulse file support in Parcel 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
+ * Installation:
13
+ * 1. Install as dev dependency:
14
+ * npm install -D pulse-js-framework
15
+ *
16
+ * 2. Configure in .parcelrc:
17
+ * {
18
+ * "extends": "@parcel/config-default",
19
+ * "transformers": {
20
+ * "*.pulse": ["pulse-js-framework/parcel"]
21
+ * }
22
+ * }
23
+ *
24
+ * 3. Use .pulse files in your project:
25
+ * import MyComponent from './MyComponent.pulse';
26
+ *
27
+ * Features:
28
+ * - ✅ Automatic .pulse file transformation
29
+ * - ✅ CSS extraction to Parcel's CSS pipeline
30
+ * - ✅ Source map generation
31
+ * - ✅ SASS/LESS/Stylus auto-detection and compilation
32
+ * - ✅ Hot Module Replacement (HMR)
33
+ * - ✅ Watch mode support
34
+ */
35
+
36
+ import { compile } from '../compiler/index.js';
37
+ import {
38
+ preprocessStylesSync,
39
+ isSassAvailable,
40
+ isLessAvailable,
41
+ isStylusAvailable,
42
+ getSassVersion,
43
+ getLessVersion,
44
+ getStylusVersion,
45
+ detectPreprocessor
46
+ } from '../compiler/preprocessor.js';
47
+ import { dirname } from 'path';
48
+
49
+ // Cache for preprocessor availability checks
50
+ let preprocessorCache = null;
51
+
52
+ /**
53
+ * Check available preprocessors once
54
+ */
55
+ function checkPreprocessors() {
56
+ if (preprocessorCache) return preprocessorCache;
57
+
58
+ preprocessorCache = {
59
+ sass: isSassAvailable(),
60
+ less: isLessAvailable(),
61
+ stylus: isStylusAvailable()
62
+ };
63
+
64
+ return preprocessorCache;
65
+ }
66
+
67
+ /**
68
+ * Transform function for Parcel
69
+ * This is the core logic that will be wrapped by Parcel's Transformer
70
+ */
71
+ export async function transformPulse({ asset, logger }) {
72
+ const source = await asset.getCode();
73
+ const filePath = asset.filePath;
74
+
75
+ // Get user options from .pulserc or plugin config
76
+ const config = await asset.getConfig(['.pulserc', '.pulserc.json'], {
77
+ packageKey: 'pulse'
78
+ });
79
+
80
+ const {
81
+ sourceMap = true,
82
+ extractCss = true,
83
+ sass: sassOptions = {},
84
+ less: lessOptions = {},
85
+ stylus: stylusOptions = {},
86
+ verbose = false
87
+ } = config || {};
88
+
89
+ // Log preprocessor availability once
90
+ if (!checkPreprocessors._logged && verbose) {
91
+ const available = checkPreprocessors();
92
+ const preprocessors = [];
93
+
94
+ if (available.sass) {
95
+ preprocessors.push(`SASS ${getSassVersion() || 'unknown'}`);
96
+ }
97
+ if (available.less) {
98
+ preprocessors.push(`LESS ${getLessVersion() || 'unknown'}`);
99
+ }
100
+ if (available.stylus) {
101
+ preprocessors.push(`Stylus ${getStylusVersion() || 'unknown'}`);
102
+ }
103
+
104
+ if (preprocessors.length > 0) {
105
+ logger.info({
106
+ message: `[Pulse] Preprocessor support: ${preprocessors.join(', ')}`
107
+ });
108
+ }
109
+
110
+ checkPreprocessors._logged = true;
111
+ }
112
+
113
+ try {
114
+ // Compile .pulse to JavaScript
115
+ const result = compile(source, {
116
+ runtime: 'pulse-js-framework/runtime',
117
+ sourceMap,
118
+ filename: filePath
119
+ });
120
+
121
+ if (!result.success) {
122
+ const errors = result.errors.map(e =>
123
+ `${e.message}${e.line ? ` at line ${e.line}` : ''}`
124
+ ).join('\n');
125
+
126
+ throw new Error(`Pulse compilation failed:\n${errors}`);
127
+ }
128
+
129
+ let outputCode = result.code;
130
+ const outputMap = result.map;
131
+
132
+ // Extract CSS from compiled output
133
+ const stylesMatch = outputCode.match(/const styles = `([\s\S]*?)`;/);
134
+
135
+ if (stylesMatch && extractCss) {
136
+ let css = stylesMatch[1];
137
+
138
+ // Check available preprocessors
139
+ const available = checkPreprocessors();
140
+ const preprocessor = detectPreprocessor(css);
141
+
142
+ // Preprocess if preprocessor detected and available
143
+ if (preprocessor !== 'none' && available[preprocessor]) {
144
+ try {
145
+ const preprocessorOptions = {
146
+ sass: sassOptions,
147
+ less: lessOptions,
148
+ stylus: stylusOptions
149
+ }[preprocessor];
150
+
151
+ const preprocessed = preprocessStylesSync(css, {
152
+ filename: filePath,
153
+ loadPaths: [dirname(filePath), ...(preprocessorOptions.loadPaths || [])],
154
+ compressed: preprocessorOptions.compressed || false,
155
+ preprocessor // Force detected preprocessor
156
+ });
157
+
158
+ css = preprocessed.css;
159
+
160
+ // Log preprocessor usage in verbose mode
161
+ if (preprocessorOptions.verbose || verbose) {
162
+ logger.verbose({
163
+ message: `[Pulse] Compiled ${preprocessor.toUpperCase()} in ${filePath}`
164
+ });
165
+ }
166
+ } catch (preprocessorError) {
167
+ // Emit warning but continue with original CSS
168
+ logger.warn({
169
+ message: `${preprocessor.toUpperCase()} compilation warning: ${preprocessorError.message}`,
170
+ filePath
171
+ });
172
+ }
173
+ }
174
+
175
+ // Create a CSS asset for Parcel to process
176
+ // This allows Parcel's CSS pipeline to handle minification, autoprefixing, etc.
177
+ asset.addDependency({
178
+ specifier: filePath + '.pulse.css',
179
+ specifierType: 'url'
180
+ });
181
+
182
+ // Emit CSS as a separate asset
183
+ await asset.addAsset({
184
+ type: 'css',
185
+ content: css,
186
+ uniqueKey: filePath + '-styles'
187
+ });
188
+
189
+ // Remove inline CSS injection from output
190
+ // Match the entire styles section and replace it
191
+ outputCode = outputCode.replace(
192
+ /\/\/ Styles[\s\S]*?\/\/ Inject styles[\s\S]*?document\.head\.appendChild\(styleEl\);/,
193
+ '// Styles extracted to CSS asset'
194
+ );
195
+ }
196
+
197
+ // Set asset type and content
198
+ asset.type = 'js';
199
+ asset.setCode(outputCode);
200
+
201
+ // Add source map if enabled
202
+ if (sourceMap && outputMap) {
203
+ asset.setMap(outputMap);
204
+ }
205
+
206
+ return [asset];
207
+ } catch (error) {
208
+ throw new Error(`Pulse transformer error: ${error.message}`);
209
+ }
210
+ }
211
+
212
+ /**
213
+ * Default export - will be wrapped by Parcel's Transformer
214
+ * We export the transform function directly for easier testing
215
+ */
216
+ export default { transform: transformPulse };