pulse-js-framework 1.7.32 → 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/index.js +0 -0
- package/cli/release.js +159 -22
- package/compiler/parser.js +93 -9
- 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/webpack-loader.js +228 -0
- package/package.json +12 -2
|
@@ -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.
|
|
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",
|
|
@@ -94,6 +94,11 @@
|
|
|
94
94
|
"types": "./types/index.d.ts",
|
|
95
95
|
"default": "./loader/vite-plugin.js"
|
|
96
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",
|
|
97
102
|
"./mobile": "./mobile/bridge/pulse-native.js",
|
|
98
103
|
"./package.json": "./package.json"
|
|
99
104
|
},
|
|
@@ -110,7 +115,7 @@
|
|
|
110
115
|
"LICENSE"
|
|
111
116
|
],
|
|
112
117
|
"scripts": {
|
|
113
|
-
"test": "
|
|
118
|
+
"test": "node scripts/run-all-tests.js",
|
|
114
119
|
"test:compiler": "node test/compiler.test.js",
|
|
115
120
|
"test:sourcemap": "node test/sourcemap.test.js",
|
|
116
121
|
"test:css-parsing": "node test/css-parsing.test.js",
|
|
@@ -168,6 +173,11 @@
|
|
|
168
173
|
"test:websocket-stress": "node test/websocket-stress.test.js",
|
|
169
174
|
"test:ssr": "node test/ssr.test.js",
|
|
170
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",
|
|
171
181
|
"build:netlify": "node scripts/build-netlify.js",
|
|
172
182
|
"version": "node scripts/sync-version.js",
|
|
173
183
|
"docs": "node cli/index.js dev docs"
|