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,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 };
|