@cssxjs/loaders 0.2.7

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/CHANGELOG.md ADDED
@@ -0,0 +1,9 @@
1
+ # v0.2.7 (Wed Nov 05 2025)
2
+
3
+ #### 🐛 Bug Fix
4
+
5
+ - fix: move css and styl loaders into a separate package @cssxjs/loaders ([@cray0000](https://github.com/cray0000))
6
+
7
+ #### Authors: 1
8
+
9
+ - Pavel Zhukov ([@cray0000](https://github.com/cray0000))
package/callLoader.js ADDED
@@ -0,0 +1,22 @@
1
+ // Simple mock to be able to call simple webpack loaders with filename substitution.
2
+ module.exports = function callLoader (loader, source, filename, options = {}) {
3
+ let isAsync = false
4
+ let resolveAsync
5
+ let rejectAsync
6
+ const markAsync = () => {
7
+ isAsync = true
8
+ return (err, result) => {
9
+ if (err) return rejectAsync(err)
10
+ resolveAsync(result)
11
+ }
12
+ }
13
+ const result = loader.call({ resourcePath: filename, query: options, async: markAsync }, source)
14
+ if (isAsync) {
15
+ return new Promise((resolve, reject) => {
16
+ resolveAsync = resolve
17
+ rejectAsync = reject
18
+ })
19
+ } else {
20
+ return result
21
+ }
22
+ }
@@ -0,0 +1,86 @@
1
+ // ref: https://github.com/kristerkari/react-native-css-transformer
2
+ const css2rn = require('@startupjs/css-to-react-native-transform').default
3
+
4
+ const EXPORT_REGEX = /:export\s*\{/
5
+
6
+ module.exports = function cssToReactNative (source) {
7
+ source = escapeExport(source)
8
+ const cssObject = css2rn(source, { parseMediaQueries: true, parsePartSelectors: true })
9
+ for (const key in cssObject.__exportProps || {}) {
10
+ cssObject[key] = parseStylValue(cssObject.__exportProps[key])
11
+ }
12
+ // save hash to use with the caching system of @startupjs/cache
13
+ cssObject.__hash__ = simpleNumericHash(JSON.stringify(cssObject))
14
+ return 'module.exports = ' + JSON.stringify(cssObject)
15
+ }
16
+
17
+ function parseStylValue (value) {
18
+ if (typeof value !== 'string') return value
19
+ // strip single quotes (stylus adds it for the topmost value)
20
+ // and parens (stylus adds them for values in a hash)
21
+ // Instead of doing a simple regex replace for both beginning and end,
22
+ // we only find beginning chars and then cut string from both sides.
23
+ // This is needed to prevent false-replacing the paren at the end of
24
+ // values like 'rgba(...)'
25
+ if (/^['"(]/.test(value)) {
26
+ const wrapsLength = value.match(/^['"(]+/)[0].length
27
+ value = value.slice(wrapsLength).slice(0, -wrapsLength)
28
+ }
29
+ // hash
30
+ if (value.charAt(0) === '{') {
31
+ const hash = JSON.parse(value)
32
+ for (const key in hash) {
33
+ hash[key] = parseStylValue(hash[key])
34
+ }
35
+ return hash
36
+ } else if (!isNaN(parseFloat(value))) {
37
+ return parseFloat(value)
38
+ } else {
39
+ return value
40
+ }
41
+ }
42
+
43
+ // Process :export properties by wrapping their values in quotes
44
+ function escapeExport (source) {
45
+ const match = source.match(EXPORT_REGEX)
46
+ if (!match) return source
47
+
48
+ // 1. find closing bracket of :export { ... }
49
+ const matchIndex = match.index
50
+ const matchStr = match[0]
51
+ const matchLength = matchStr.length
52
+ const start = matchIndex + matchLength
53
+ let openBr = 1 // Count opened brackets, we start from one already opened
54
+ let end
55
+
56
+ for (let i = start; i < source.length; i++) {
57
+ if (source.charAt(i) === '}') {
58
+ --openBr
59
+ } else if (source.charAt(i) === '{') {
60
+ ++openBr
61
+ }
62
+
63
+ if (openBr <= 0) {
64
+ end = i
65
+ break
66
+ }
67
+ }
68
+ if (!end) return source
69
+
70
+ // 2. escape all exported values
71
+ const properties = source
72
+ .slice(start, end)
73
+ .split(';')
74
+ .map(line => line.replace(/(:\s+)([^'"].*[^'"])$/, '$1\'$2\''))
75
+ .join(';')
76
+
77
+ source = source.slice(0, start) + properties + source.slice(end)
78
+
79
+ return source
80
+ }
81
+
82
+ // ref: https://gist.github.com/hyamamoto/fd435505d29ebfa3d9716fd2be8d42f0?permalink_comment_id=2694461#gistcomment-2694461
83
+ function simpleNumericHash (s) {
84
+ for (var i = 0, h = 0; i < s.length; i++) h = Math.imul(31, h) + s.charCodeAt(i) | 0
85
+ return h
86
+ }
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@cssxjs/loaders",
3
+ "version": "0.2.7",
4
+ "description": "Webpack-compatible loaders for CSSX styles in React Native and Web bundlers",
5
+ "exports": {
6
+ "./callLoader": "./callLoader.js",
7
+ "./stylusToCssLoader": "./stylusToCssLoader.js",
8
+ "./cssToReactNativeLoader": "./cssToReactNativeLoader.js"
9
+ },
10
+ "publishConfig": {
11
+ "access": "public"
12
+ },
13
+ "scripts": {
14
+ "test": "echo 'No tests yet' && exit 0"
15
+ },
16
+ "author": {
17
+ "name": "Pavel Zhukov",
18
+ "email": "cray0000@gmail.com"
19
+ },
20
+ "license": "MIT",
21
+ "dependencies": {
22
+ "@startupjs/css-to-react-native-transform": "^2.1.0-1",
23
+ "stylus": "0.64.0"
24
+ },
25
+ "gitHead": "df9297432a485b18a13127558a62df7be263cac2"
26
+ }
@@ -0,0 +1,43 @@
1
+ // add a new custom unit 'u' which is equal to 8px
2
+ const units = require('stylus/lib/units.js')
3
+ const Unit = require('stylus/lib/nodes/unit.js')
4
+ let patched = false
5
+
6
+ module.exports = function patchStylusAddUnit () {
7
+ if (patched) return
8
+ patched = true
9
+
10
+ // add the unit itself
11
+ if (!units.includes('u')) units.push('u')
12
+
13
+ /**
14
+ * a monkey patch to convert the 'type' to be dynamic setter/getter
15
+ * with the real value stored in a private symbol. This simulates having 'u' unit converted to 'px' * 8 on assignment.
16
+ *
17
+ * Original constructor is the following:
18
+ *
19
+ * constructor(val, type) {
20
+ * super();
21
+ * this.val = val;
22
+ * this.type = type;
23
+ * }
24
+ *
25
+ * this.val and this.type are never reassigned.
26
+ * This make it possible to hack the this.type assignment specifically by auto-multiplying the this.val by 8
27
+ * during the attempt to assign the 'u' to this.type
28
+ */
29
+
30
+ const TYPE = Symbol('unit.type')
31
+ Object.defineProperty(Unit.prototype, 'type', {
32
+ configurable: true,
33
+ enumerable: true,
34
+ get () { return this[TYPE] },
35
+ set (t) {
36
+ if (t === 'u') {
37
+ t = 'px'
38
+ if (typeof this.val === 'number') this.val = this.val * 8
39
+ }
40
+ this[TYPE] = t
41
+ }
42
+ })
43
+ }
@@ -0,0 +1,70 @@
1
+ // ref: https://github.com/kristerkari/react-native-stylus-transformer
2
+ const { existsSync } = require('fs')
3
+ const { join } = require('path')
4
+ const stylus = require('stylus')
5
+ const patchStylusAddUnit = require('./patches/patchStylusAddUnit.js')
6
+
7
+ // apply patches
8
+ patchStylusAddUnit()
9
+
10
+ const PROJECT_STYLES_PATH = join(process.cwd(), 'styles/index.styl')
11
+ let UI_STYLES_PATH
12
+
13
+ function renderToCSS (src, filename, { platform } = {}) {
14
+ const compiler = stylus(src)
15
+ compiler.set('filename', filename)
16
+
17
+ if (platform) {
18
+ compiler.define('$PLATFORM', platform)
19
+ compiler.define(`__${platform.toUpperCase()}__`, true)
20
+ }
21
+
22
+ if (checkUiStylesExist()) {
23
+ compiler.import(UI_STYLES_PATH)
24
+ }
25
+
26
+ // TODO: Make this a setting
27
+ if (checkProjectStylesExist()) {
28
+ compiler.import(PROJECT_STYLES_PATH)
29
+ }
30
+
31
+ let compiled
32
+ compiler.render(function (err, res) {
33
+ if (err) {
34
+ throw new Error(err)
35
+ }
36
+ compiled = res
37
+ })
38
+
39
+ return compiled
40
+ }
41
+
42
+ module.exports = function stylusToReactNative (source) {
43
+ return renderToCSS(source, this.resourcePath, this.query)
44
+ }
45
+
46
+ // check if @startupjs/ui is being used to load styles file from it, cache result for 5 seconds
47
+ let uiStylesExist
48
+ let uiStylesLastChecked = 0
49
+ function checkUiStylesExist () {
50
+ if (uiStylesLastChecked + 5000 > Date.now()) return uiStylesExist
51
+ uiStylesLastChecked = Date.now()
52
+ try {
53
+ // TODO: make this configurable
54
+ UI_STYLES_PATH = join(require.resolve('@startupjs/ui'), '../styles/index.styl')
55
+ uiStylesExist = existsSync(UI_STYLES_PATH)
56
+ } catch {
57
+ uiStylesExist = false
58
+ }
59
+ return uiStylesExist
60
+ }
61
+
62
+ // check if project styles file exist, cache result for 5 seconds
63
+ let projectStylesExist
64
+ let projectStylesLastChecked = 0
65
+ function checkProjectStylesExist () {
66
+ if (projectStylesLastChecked + 5000 > Date.now()) return projectStylesExist
67
+ projectStylesLastChecked = Date.now()
68
+ projectStylesExist = existsSync(PROJECT_STYLES_PATH)
69
+ return projectStylesExist
70
+ }