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,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",
|
|
@@ -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": "
|
|
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"
|