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.
- package/cli/build.js +34 -1
- package/cli/dev.js +54 -3
- package/cli/index.js +0 -0
- package/cli/release.js +159 -22
- package/compiler/parser.js +93 -9
- package/compiler/preprocessor.js +819 -0
- package/compiler/transformer/expressions.js +26 -6
- package/loader/README.md +509 -0
- package/loader/esbuild-plugin.js +251 -0
- package/loader/parcel-plugin.js +216 -0
- package/loader/rollup-plugin.js +259 -0
- package/loader/swc-plugin.js +286 -0
- package/loader/vite-plugin.js +57 -12
- package/loader/webpack-loader.js +228 -0
- package/package.json +14 -2
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pulse Rollup Plugin
|
|
3
|
+
*
|
|
4
|
+
* Enables .pulse file support in Rollup 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 rollup.config.js:
|
|
13
|
+
* ```js
|
|
14
|
+
* import pulsePlugin from 'pulse-js-framework/rollup';
|
|
15
|
+
*
|
|
16
|
+
* export default {
|
|
17
|
+
* input: 'src/main.js',
|
|
18
|
+
* output: { file: 'dist/bundle.js', format: 'es' },
|
|
19
|
+
* plugins: [
|
|
20
|
+
* pulsePlugin({
|
|
21
|
+
* sourceMap: true,
|
|
22
|
+
* extractCss: 'dist/bundle.css',
|
|
23
|
+
* sass: { loadPaths: ['src/styles'] }
|
|
24
|
+
* })
|
|
25
|
+
* ]
|
|
26
|
+
* };
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import { compile } from '../compiler/index.js';
|
|
31
|
+
import {
|
|
32
|
+
preprocessStylesSync,
|
|
33
|
+
isSassAvailable,
|
|
34
|
+
isLessAvailable,
|
|
35
|
+
isStylusAvailable,
|
|
36
|
+
getSassVersion,
|
|
37
|
+
getLessVersion,
|
|
38
|
+
getStylusVersion,
|
|
39
|
+
detectPreprocessor
|
|
40
|
+
} from '../compiler/preprocessor.js';
|
|
41
|
+
import { dirname } from 'path';
|
|
42
|
+
|
|
43
|
+
// Cache for preprocessor availability checks
|
|
44
|
+
let preprocessorCache = null;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Check available preprocessors once
|
|
48
|
+
*/
|
|
49
|
+
function checkPreprocessors() {
|
|
50
|
+
if (preprocessorCache) return preprocessorCache;
|
|
51
|
+
|
|
52
|
+
preprocessorCache = {
|
|
53
|
+
sass: isSassAvailable(),
|
|
54
|
+
less: isLessAvailable(),
|
|
55
|
+
stylus: isStylusAvailable()
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
return preprocessorCache;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Create Pulse Rollup plugin
|
|
63
|
+
*/
|
|
64
|
+
export default function pulsePlugin(options = {}) {
|
|
65
|
+
const {
|
|
66
|
+
include = /\.pulse$/,
|
|
67
|
+
exclude = /node_modules/,
|
|
68
|
+
sourceMap = true,
|
|
69
|
+
extractCss = null, // Path to output CSS file, or null for inline
|
|
70
|
+
sass: sassOptions = {},
|
|
71
|
+
less: lessOptions = {},
|
|
72
|
+
stylus: stylusOptions = {}
|
|
73
|
+
} = options;
|
|
74
|
+
|
|
75
|
+
const filter = createFilter(include, exclude);
|
|
76
|
+
|
|
77
|
+
// Accumulated CSS from all .pulse files
|
|
78
|
+
let accumulatedCss = '';
|
|
79
|
+
let cssEmitted = false;
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
name: 'pulse',
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Log preprocessor availability on build start
|
|
86
|
+
*/
|
|
87
|
+
buildStart() {
|
|
88
|
+
// Reset accumulated CSS
|
|
89
|
+
accumulatedCss = '';
|
|
90
|
+
cssEmitted = false;
|
|
91
|
+
|
|
92
|
+
// Check preprocessor availability
|
|
93
|
+
const available = checkPreprocessors();
|
|
94
|
+
const preprocessors = [];
|
|
95
|
+
|
|
96
|
+
if (available.sass) {
|
|
97
|
+
preprocessors.push(`SASS ${getSassVersion() || 'unknown'}`);
|
|
98
|
+
}
|
|
99
|
+
if (available.less) {
|
|
100
|
+
preprocessors.push(`LESS ${getLessVersion() || 'unknown'}`);
|
|
101
|
+
}
|
|
102
|
+
if (available.stylus) {
|
|
103
|
+
preprocessors.push(`Stylus ${getStylusVersion() || 'unknown'}`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (preprocessors.length > 0) {
|
|
107
|
+
console.log(`[Pulse Rollup] Preprocessor support: ${preprocessors.join(', ')}`);
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Resolve .pulse files
|
|
113
|
+
*/
|
|
114
|
+
resolveId(id, importer) {
|
|
115
|
+
// Handle .pulse imports
|
|
116
|
+
if (id.endsWith('.pulse')) {
|
|
117
|
+
return null; // Let Rollup handle it
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Check if a .js import has a corresponding .pulse file
|
|
121
|
+
if (id.endsWith('.js') && importer) {
|
|
122
|
+
const pulseId = id.replace(/\.js$/, '.pulse');
|
|
123
|
+
// Let Rollup's resolution handle this
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return null;
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Transform .pulse files to JavaScript
|
|
132
|
+
*/
|
|
133
|
+
transform(source, id) {
|
|
134
|
+
if (!filter(id)) return null;
|
|
135
|
+
if (!id.endsWith('.pulse')) return null;
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
// Compile .pulse to JavaScript
|
|
139
|
+
const result = compile(source, {
|
|
140
|
+
runtime: 'pulse-js-framework/runtime',
|
|
141
|
+
sourceMap,
|
|
142
|
+
filename: id
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
if (!result.success) {
|
|
146
|
+
const errors = result.errors.map(e =>
|
|
147
|
+
`${e.message}${e.line ? ` at line ${e.line}` : ''}`
|
|
148
|
+
).join('\n');
|
|
149
|
+
|
|
150
|
+
this.error(`Pulse compilation failed:\n${errors}`);
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
let outputCode = result.code;
|
|
155
|
+
let outputMap = result.map;
|
|
156
|
+
|
|
157
|
+
// Extract CSS from compiled output
|
|
158
|
+
const stylesMatch = outputCode.match(/const styles = `([\s\S]*?)`;/);
|
|
159
|
+
|
|
160
|
+
if (stylesMatch) {
|
|
161
|
+
let css = stylesMatch[1];
|
|
162
|
+
|
|
163
|
+
// Check available preprocessors
|
|
164
|
+
const available = checkPreprocessors();
|
|
165
|
+
const preprocessor = detectPreprocessor(css);
|
|
166
|
+
|
|
167
|
+
// Preprocess if preprocessor detected and available
|
|
168
|
+
if (preprocessor !== 'none' && available[preprocessor]) {
|
|
169
|
+
try {
|
|
170
|
+
const preprocessorOptions = {
|
|
171
|
+
sass: sassOptions,
|
|
172
|
+
less: lessOptions,
|
|
173
|
+
stylus: stylusOptions
|
|
174
|
+
}[preprocessor];
|
|
175
|
+
|
|
176
|
+
const preprocessed = preprocessStylesSync(css, {
|
|
177
|
+
filename: id,
|
|
178
|
+
loadPaths: [dirname(id), ...(preprocessorOptions.loadPaths || [])],
|
|
179
|
+
compressed: preprocessorOptions.compressed || false,
|
|
180
|
+
preprocessor // Force detected preprocessor
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
css = preprocessed.css;
|
|
184
|
+
|
|
185
|
+
// Log preprocessor usage in verbose mode
|
|
186
|
+
if (preprocessorOptions.verbose) {
|
|
187
|
+
console.log(`[Pulse] Compiled ${preprocessor.toUpperCase()} in ${id}`);
|
|
188
|
+
}
|
|
189
|
+
} catch (preprocessorError) {
|
|
190
|
+
// Emit warning but continue with original CSS
|
|
191
|
+
this.warn(`${preprocessor.toUpperCase()} compilation warning: ${preprocessorError.message}`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (extractCss) {
|
|
196
|
+
// Accumulate CSS for later emission
|
|
197
|
+
accumulatedCss += `/* ${id} */\n${css}\n\n`;
|
|
198
|
+
|
|
199
|
+
// Remove inline CSS injection from output
|
|
200
|
+
outputCode = outputCode.replace(
|
|
201
|
+
/\/\/ Styles\nconst styles = `[\s\S]*?`;\n\/\/ Inject styles\nconst styleEl = document\.createElement\("style"\);\nstyleEl\.textContent = styles;\ndocument\.head\.appendChild\(styleEl\);/,
|
|
202
|
+
'// Styles extracted to CSS file'
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
// else: keep inline CSS injection
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return {
|
|
209
|
+
code: outputCode,
|
|
210
|
+
map: sourceMap ? outputMap : null
|
|
211
|
+
};
|
|
212
|
+
} catch (error) {
|
|
213
|
+
this.error(`Pulse plugin error: ${error.message}`);
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Emit accumulated CSS as asset
|
|
220
|
+
*/
|
|
221
|
+
generateBundle() {
|
|
222
|
+
if (extractCss && accumulatedCss && !cssEmitted) {
|
|
223
|
+
this.emitFile({
|
|
224
|
+
type: 'asset',
|
|
225
|
+
fileName: extractCss,
|
|
226
|
+
source: accumulatedCss
|
|
227
|
+
});
|
|
228
|
+
cssEmitted = true;
|
|
229
|
+
console.log(`[Pulse] Emitted CSS to ${extractCss}`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Create filter function for include/exclude patterns
|
|
237
|
+
* Simple implementation to avoid dependency on @rollup/pluginutils
|
|
238
|
+
*/
|
|
239
|
+
function createFilter(include, exclude) {
|
|
240
|
+
return (id) => {
|
|
241
|
+
// Check exclude first
|
|
242
|
+
if (exclude) {
|
|
243
|
+
if (exclude instanceof RegExp && exclude.test(id)) return false;
|
|
244
|
+
if (Array.isArray(exclude) && exclude.some(pattern =>
|
|
245
|
+
pattern instanceof RegExp ? pattern.test(id) : id.includes(pattern)
|
|
246
|
+
)) return false;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Check include
|
|
250
|
+
if (include) {
|
|
251
|
+
if (include instanceof RegExp) return include.test(id);
|
|
252
|
+
if (Array.isArray(include)) return include.some(pattern =>
|
|
253
|
+
pattern instanceof RegExp ? pattern.test(id) : id.includes(pattern)
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return true;
|
|
258
|
+
};
|
|
259
|
+
}
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pulse SWC Plugin
|
|
3
|
+
*
|
|
4
|
+
* Enables .pulse file support in SWC-based build pipelines
|
|
5
|
+
* Provides transform functions and a plugin interface for custom build scripts
|
|
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 with direct transform API:
|
|
13
|
+
* ```js
|
|
14
|
+
* import { transformPulseFile } from 'pulse-js-framework/swc';
|
|
15
|
+
*
|
|
16
|
+
* const result = transformPulseFile('src/App.pulse', {
|
|
17
|
+
* sourceMap: true,
|
|
18
|
+
* sass: { loadPaths: ['src/styles'] }
|
|
19
|
+
* });
|
|
20
|
+
* console.log(result.code); // Compiled JavaScript
|
|
21
|
+
* console.log(result.css); // Extracted CSS (or null)
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* Usage with plugin interface:
|
|
25
|
+
* ```js
|
|
26
|
+
* import pulsePlugin from 'pulse-js-framework/swc';
|
|
27
|
+
*
|
|
28
|
+
* const plugin = pulsePlugin({ extractCss: 'dist/bundle.css' });
|
|
29
|
+
* plugin.buildStart();
|
|
30
|
+
* const result = plugin.transform(source, 'src/App.pulse');
|
|
31
|
+
* plugin.buildEnd(); // Writes accumulated CSS
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* Batch processing:
|
|
35
|
+
* ```js
|
|
36
|
+
* import { buildPulseFiles } from 'pulse-js-framework/swc';
|
|
37
|
+
*
|
|
38
|
+
* const results = buildPulseFiles(['src/App.pulse', 'src/Home.pulse'], {
|
|
39
|
+
* extractCss: 'dist/bundle.css'
|
|
40
|
+
* });
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
import { compile } from '../compiler/index.js';
|
|
45
|
+
import {
|
|
46
|
+
preprocessStylesSync,
|
|
47
|
+
isSassAvailable,
|
|
48
|
+
isLessAvailable,
|
|
49
|
+
isStylusAvailable,
|
|
50
|
+
getSassVersion,
|
|
51
|
+
getLessVersion,
|
|
52
|
+
getStylusVersion,
|
|
53
|
+
detectPreprocessor
|
|
54
|
+
} from '../compiler/preprocessor.js';
|
|
55
|
+
import { dirname, resolve } from 'path';
|
|
56
|
+
import { readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
57
|
+
|
|
58
|
+
// Cache for preprocessor availability checks
|
|
59
|
+
let preprocessorCache = null;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Check available preprocessors once
|
|
63
|
+
*/
|
|
64
|
+
function checkPreprocessors() {
|
|
65
|
+
if (preprocessorCache) return preprocessorCache;
|
|
66
|
+
|
|
67
|
+
preprocessorCache = {
|
|
68
|
+
sass: isSassAvailable(),
|
|
69
|
+
less: isLessAvailable(),
|
|
70
|
+
stylus: isStylusAvailable()
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
return preprocessorCache;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Create Pulse SWC plugin
|
|
78
|
+
*/
|
|
79
|
+
export default function pulsePlugin(options = {}) {
|
|
80
|
+
const {
|
|
81
|
+
sourceMap = true,
|
|
82
|
+
extractCss = null, // Path to output CSS file, or null for inline
|
|
83
|
+
sass: sassOptions = {},
|
|
84
|
+
less: lessOptions = {},
|
|
85
|
+
stylus: stylusOptions = {}
|
|
86
|
+
} = options;
|
|
87
|
+
|
|
88
|
+
// Accumulated CSS from all .pulse files
|
|
89
|
+
let accumulatedCss = '';
|
|
90
|
+
let buildStarted = false;
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
name: 'pulse',
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Reset accumulated CSS and log preprocessor availability
|
|
97
|
+
*/
|
|
98
|
+
buildStart() {
|
|
99
|
+
accumulatedCss = '';
|
|
100
|
+
|
|
101
|
+
if (!buildStarted) {
|
|
102
|
+
const available = checkPreprocessors();
|
|
103
|
+
const preprocessors = [];
|
|
104
|
+
|
|
105
|
+
if (available.sass) {
|
|
106
|
+
preprocessors.push(`SASS ${getSassVersion() || 'unknown'}`);
|
|
107
|
+
}
|
|
108
|
+
if (available.less) {
|
|
109
|
+
preprocessors.push(`LESS ${getLessVersion() || 'unknown'}`);
|
|
110
|
+
}
|
|
111
|
+
if (available.stylus) {
|
|
112
|
+
preprocessors.push(`Stylus ${getStylusVersion() || 'unknown'}`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (preprocessors.length > 0) {
|
|
116
|
+
console.log(`[Pulse SWC] Preprocessor support: ${preprocessors.join(', ')}`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
buildStarted = true;
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Transform a .pulse file source to JavaScript
|
|
125
|
+
* @param {string} source - The .pulse file source code
|
|
126
|
+
* @param {string} filePath - The file path for error reporting and preprocessor resolution
|
|
127
|
+
* @returns {{ code: string|null, map: object|null, css: string|null, error: string|null }}
|
|
128
|
+
*/
|
|
129
|
+
transform(source, filePath) {
|
|
130
|
+
try {
|
|
131
|
+
// Compile .pulse to JavaScript
|
|
132
|
+
const result = compile(source, {
|
|
133
|
+
runtime: 'pulse-js-framework/runtime',
|
|
134
|
+
sourceMap,
|
|
135
|
+
filename: filePath
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
if (!result.success) {
|
|
139
|
+
const errors = result.errors.map(e =>
|
|
140
|
+
`${e.message}${e.line ? ` at line ${e.line}` : ''}`
|
|
141
|
+
).join('\n');
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
code: null,
|
|
145
|
+
map: null,
|
|
146
|
+
css: null,
|
|
147
|
+
error: `Pulse compilation failed:\n${errors}`
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
let outputCode = result.code;
|
|
152
|
+
let extractedCss = null;
|
|
153
|
+
|
|
154
|
+
// Extract CSS from compiled output
|
|
155
|
+
const stylesMatch = outputCode.match(/const styles = `([\s\S]*?)`;/);
|
|
156
|
+
|
|
157
|
+
if (stylesMatch) {
|
|
158
|
+
let css = stylesMatch[1];
|
|
159
|
+
|
|
160
|
+
// Check available preprocessors
|
|
161
|
+
const available = checkPreprocessors();
|
|
162
|
+
const preprocessor = detectPreprocessor(css);
|
|
163
|
+
|
|
164
|
+
// Preprocess if preprocessor detected and available
|
|
165
|
+
if (preprocessor !== 'none' && available[preprocessor]) {
|
|
166
|
+
try {
|
|
167
|
+
const preprocessorOptions = {
|
|
168
|
+
sass: sassOptions,
|
|
169
|
+
less: lessOptions,
|
|
170
|
+
stylus: stylusOptions
|
|
171
|
+
}[preprocessor];
|
|
172
|
+
|
|
173
|
+
const preprocessed = preprocessStylesSync(css, {
|
|
174
|
+
filename: filePath,
|
|
175
|
+
loadPaths: [dirname(filePath), ...(preprocessorOptions.loadPaths || [])],
|
|
176
|
+
compressed: preprocessorOptions.compressed || false,
|
|
177
|
+
preprocessor // Force detected preprocessor
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
css = preprocessed.css;
|
|
181
|
+
|
|
182
|
+
// Log preprocessor usage in verbose mode
|
|
183
|
+
if (preprocessorOptions.verbose) {
|
|
184
|
+
console.log(`[Pulse] Compiled ${preprocessor.toUpperCase()} in ${filePath}`);
|
|
185
|
+
}
|
|
186
|
+
} catch (preprocessorError) {
|
|
187
|
+
// Emit warning but continue with original CSS
|
|
188
|
+
console.warn(`[Pulse SWC] ${preprocessor.toUpperCase()} compilation warning: ${preprocessorError.message}`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
extractedCss = css;
|
|
193
|
+
|
|
194
|
+
if (extractCss) {
|
|
195
|
+
// Accumulate CSS for later emission
|
|
196
|
+
accumulatedCss += `/* ${filePath} */\n${css}\n\n`;
|
|
197
|
+
|
|
198
|
+
// Remove inline CSS injection from output
|
|
199
|
+
outputCode = outputCode.replace(
|
|
200
|
+
/\/\/ Styles\nconst styles = `[\s\S]*?`;\n\/\/ Inject styles\nconst styleEl = document\.createElement\("style"\);\nstyleEl\.textContent = styles;\ndocument\.head\.appendChild\(styleEl\);/,
|
|
201
|
+
'// Styles extracted to CSS file'
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
// else: keep inline CSS injection
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return {
|
|
208
|
+
code: outputCode,
|
|
209
|
+
map: result.sourceMap || null,
|
|
210
|
+
css: extractedCss,
|
|
211
|
+
error: null
|
|
212
|
+
};
|
|
213
|
+
} catch (error) {
|
|
214
|
+
return {
|
|
215
|
+
code: null,
|
|
216
|
+
map: null,
|
|
217
|
+
css: null,
|
|
218
|
+
error: `Pulse plugin error: ${error.message}`
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Write accumulated CSS to disk
|
|
225
|
+
* Call after all transforms are complete
|
|
226
|
+
*/
|
|
227
|
+
buildEnd() {
|
|
228
|
+
if (extractCss && accumulatedCss) {
|
|
229
|
+
try {
|
|
230
|
+
const cssPath = resolve(typeof extractCss === 'string' ? extractCss : 'dist/bundle.css');
|
|
231
|
+
const cssDir = dirname(cssPath);
|
|
232
|
+
mkdirSync(cssDir, { recursive: true });
|
|
233
|
+
writeFileSync(cssPath, accumulatedCss, 'utf8');
|
|
234
|
+
console.log(`[Pulse] Emitted CSS to ${extractCss}`);
|
|
235
|
+
} catch (writeError) {
|
|
236
|
+
console.error(`[Pulse] Failed to write CSS file: ${writeError.message}`);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Transform a .pulse file to JavaScript (standalone function)
|
|
245
|
+
* @param {string} filePath - Path to the .pulse file
|
|
246
|
+
* @param {object} options - Plugin options
|
|
247
|
+
* @returns {{ code: string|null, map: object|null, css: string|null, error: string|null }}
|
|
248
|
+
*/
|
|
249
|
+
export function transformPulseFile(filePath, options = {}) {
|
|
250
|
+
const resolvedPath = resolve(filePath);
|
|
251
|
+
const source = readFileSync(resolvedPath, 'utf8');
|
|
252
|
+
return transformPulseCode(source, { ...options, filename: resolvedPath });
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Transform .pulse source code to JavaScript (standalone function)
|
|
257
|
+
* @param {string} source - The .pulse source code
|
|
258
|
+
* @param {object} options - Plugin options (plus optional `filename`)
|
|
259
|
+
* @returns {{ code: string|null, map: object|null, css: string|null, error: string|null }}
|
|
260
|
+
*/
|
|
261
|
+
export function transformPulseCode(source, options = {}) {
|
|
262
|
+
const { filename = 'unknown.pulse', ...pluginOptions } = options;
|
|
263
|
+
const plugin = pulsePlugin({ ...pluginOptions, extractCss: null });
|
|
264
|
+
return plugin.transform(source, filename);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Batch process multiple .pulse files
|
|
269
|
+
* @param {string[]} files - Array of .pulse file paths
|
|
270
|
+
* @param {object} options - Plugin options
|
|
271
|
+
* @returns {Array<{ file: string, code: string|null, map: object|null, css: string|null, error: string|null }>}
|
|
272
|
+
*/
|
|
273
|
+
export function buildPulseFiles(files, options = {}) {
|
|
274
|
+
const plugin = pulsePlugin(options);
|
|
275
|
+
plugin.buildStart();
|
|
276
|
+
|
|
277
|
+
const results = files.map(filePath => {
|
|
278
|
+
const resolvedPath = resolve(filePath);
|
|
279
|
+
const source = readFileSync(resolvedPath, 'utf8');
|
|
280
|
+
const result = plugin.transform(source, resolvedPath);
|
|
281
|
+
return { file: filePath, ...result };
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
plugin.buildEnd();
|
|
285
|
+
return results;
|
|
286
|
+
}
|
package/loader/vite-plugin.js
CHANGED
|
@@ -4,11 +4,17 @@
|
|
|
4
4
|
* Enables .pulse file support in Vite projects
|
|
5
5
|
* Extracts CSS to virtual .css modules so Vite's CSS pipeline handles them
|
|
6
6
|
* (prevents JS minifier from corrupting CSS in template literals)
|
|
7
|
+
*
|
|
8
|
+
* SASS/SCSS Support:
|
|
9
|
+
* - If `sass` is installed in the user's project, SCSS syntax in style blocks
|
|
10
|
+
* is automatically compiled before being passed to Vite's CSS pipeline
|
|
11
|
+
* - No configuration needed - just install sass: `npm install -D sass`
|
|
7
12
|
*/
|
|
8
13
|
|
|
9
14
|
import { compile } from '../compiler/index.js';
|
|
10
15
|
import { existsSync } from 'fs';
|
|
11
16
|
import { resolve, dirname } from 'path';
|
|
17
|
+
import { preprocessStylesSync, isSassAvailable, getSassVersion } from '../compiler/preprocessor.js';
|
|
12
18
|
|
|
13
19
|
// Virtual module ID for extracted CSS (uses .css extension so Vite treats it as CSS)
|
|
14
20
|
const VIRTUAL_CSS_SUFFIX = '.pulse.css';
|
|
@@ -19,16 +25,37 @@ const VIRTUAL_CSS_SUFFIX = '.pulse.css';
|
|
|
19
25
|
export default function pulsePlugin(options = {}) {
|
|
20
26
|
const {
|
|
21
27
|
exclude = /node_modules/,
|
|
22
|
-
sourceMap = true
|
|
28
|
+
sourceMap = true,
|
|
29
|
+
// SASS options
|
|
30
|
+
sass: sassOptions = {}
|
|
23
31
|
} = options;
|
|
24
32
|
|
|
25
33
|
// Store extracted CSS for each .pulse module
|
|
26
34
|
const cssMap = new Map();
|
|
27
35
|
|
|
36
|
+
// Check for sass availability once at startup
|
|
37
|
+
let sassAvailable = false;
|
|
38
|
+
let sassVersion = null;
|
|
39
|
+
|
|
28
40
|
return {
|
|
29
41
|
name: 'vite-plugin-pulse',
|
|
30
42
|
enforce: 'pre',
|
|
31
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Log sass availability on build start
|
|
46
|
+
*/
|
|
47
|
+
buildStart() {
|
|
48
|
+
// Clear CSS map on new build
|
|
49
|
+
cssMap.clear();
|
|
50
|
+
|
|
51
|
+
// Check sass availability
|
|
52
|
+
sassAvailable = isSassAvailable();
|
|
53
|
+
if (sassAvailable) {
|
|
54
|
+
sassVersion = getSassVersion();
|
|
55
|
+
console.log(`[Pulse] SASS support enabled (sass ${sassVersion || 'unknown'})`);
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
|
|
32
59
|
/**
|
|
33
60
|
* Resolve .pulse files and virtual CSS modules
|
|
34
61
|
*/
|
|
@@ -107,9 +134,27 @@ export default function pulsePlugin(options = {}) {
|
|
|
107
134
|
// Extract CSS from compiled output and move to virtual CSS module
|
|
108
135
|
const stylesMatch = outputCode.match(/const styles = `([\s\S]*?)`;/);
|
|
109
136
|
if (stylesMatch) {
|
|
110
|
-
|
|
137
|
+
let css = stylesMatch[1];
|
|
111
138
|
const virtualCssId = id + '.css';
|
|
112
139
|
|
|
140
|
+
// Preprocess SASS/SCSS if detected and sass is available
|
|
141
|
+
if (sassAvailable) {
|
|
142
|
+
try {
|
|
143
|
+
const preprocessed = preprocessStylesSync(css, {
|
|
144
|
+
filename: id,
|
|
145
|
+
loadPaths: [dirname(id), ...(sassOptions.loadPaths || [])],
|
|
146
|
+
compressed: sassOptions.compressed || false
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
if (preprocessed.wasSass) {
|
|
150
|
+
css = preprocessed.css;
|
|
151
|
+
}
|
|
152
|
+
} catch (sassError) {
|
|
153
|
+
this.warn(`SASS compilation warning in ${id}: ${sassError.message}`);
|
|
154
|
+
// Continue with original CSS if SASS fails
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
113
158
|
// Store CSS for the virtual module loader
|
|
114
159
|
cssMap.set(id, css);
|
|
115
160
|
|
|
@@ -168,21 +213,21 @@ export default function pulsePlugin(options = {}) {
|
|
|
168
213
|
},
|
|
169
214
|
|
|
170
215
|
/**
|
|
171
|
-
* Configure dev server
|
|
216
|
+
* Configure dev server - log sass status on start
|
|
172
217
|
*/
|
|
173
218
|
configureServer(server) {
|
|
219
|
+
// Check sass on server start if not already checked
|
|
220
|
+
if (!sassAvailable) {
|
|
221
|
+
sassAvailable = isSassAvailable();
|
|
222
|
+
if (sassAvailable) {
|
|
223
|
+
sassVersion = getSassVersion();
|
|
224
|
+
console.log(`[Pulse] SASS support enabled (sass ${sassVersion || 'unknown'})`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
174
228
|
server.middlewares.use((_req, _res, next) => {
|
|
175
|
-
// Add any custom middleware here
|
|
176
229
|
next();
|
|
177
230
|
});
|
|
178
|
-
},
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Build hooks
|
|
182
|
-
*/
|
|
183
|
-
buildStart() {
|
|
184
|
-
// Clear CSS map on new build
|
|
185
|
-
cssMap.clear();
|
|
186
231
|
}
|
|
187
232
|
};
|
|
188
233
|
}
|